Space Game

So many systems!

Project Name Screenshot

Role

Lead Developer/Programmer, Designer

Engine/Tools

Java and Processing

Duration

1 month

Team Size

Only me - Solo project for university

Project Overview

This game was the final project for the course Modelation and Simulation of Natural Systems. Throughout the course I've learned many topics such as Physics Simulation, Particle Systems, Autonomous Agents, Cellular Automata and Fractals. The systems I ended up implementing into my game were:

You'll be able to learn how I developed these systems in much more detail in the next section!

Key Systems

Physics Simulation

RigidBody and Collision System

Boids

Autonomous agent that is the brain behind the enemies of this game implementing seek and wander behaviours

Particle System

A simple particle system used for the exhaust of the ship

Cellular Automata

Procedural technique used to make a cave- -like map

Physics Simulation

The Body

Before we can have physics in a game we need a way to represent our "things" or our "objects" like the player, enemy, etc, in the simulated physical world of our game. With that in mind I implemented a RigidBody that will represent all the entities that move in my game: the player, the enemy and even projectiles.

A rigid body means that it will not be possible to deform the body, in other words, the distance between any points of a given body stays the same no matter what forces are applied to it. However, the body can move throughout space when forces are applied to it. In this game both friction and velocities are simulated by the rigidBody. For this to be possible we need the following vectors:

  • Position Vector with x and y coordinates to represent it's position in space
  • 2d Velocity Vector
  • 2d Acceleration Vector
  • A method to apply force to the body once
  • A method to simulate all forces applied to the body

Now that we have a way to represent our body in the virtual world we need a way to be able to, move it move it, through it. In the next diagram you can see how the movement was planned out:


The RigidBody

The RigidBody class has 3 types of movement that each children can use:

  • Position - case Position where the Vector Position is directly acessed and altered, equivalent to teleportation
  • Velocity - generic case where the body has velocity and acceleration
  • Force - where each instance of a RigidBody can override the main way of moving

The following code represents the types of movement mentioned in the class RigidBody:

       public void move(float dt, ControlType ct, PApplet p) {
            switch (ct) {
                    case POSITION:
                        break; //not useful for this game
                                
                    case VELOCITY:
                        vel.add(acc.mult(dt));
                        pos.add(PVector.mult(vel, dt));
                        // acc.mult(0);
                        break;
                
                    case FORCE:
                        force(dt, p); // each child (extends) of RididBody will have different ways of moving
                        break; // player is different from enemy for example
                    }
                }

        protected abstract void force(float dt, PApplet p); 
                
                        

Movement Implementation

The classes that will extend rigidBody will either use the Velocity type of movement, like particle systems, and classes like the player will override the force method to implement its own way of moving. You can see this in the following code, that is the implementation of the stock and flow diagram:

     protected void force(float dt, PApplet p) {
            PVector frictionForce = PVector.mult(vel, -frictionCoeff);
            displayVectors(p, d, frictionForce, 255, 255, 255);
        
            vel.add(frictionForce);
            pos.add(PVector.mult(vel, dt));
            vel.add(PVector.mult(acc, dt));
    
            PVector newForce = PVector.mult(desiredDir(), speed * accelerationBoost);
            displayVectors(p, d, getDesiredDir(), 0, 255, 0);
    
            // Apply logarithmic acceleration decay based on current speed
            float speedFactor = 1.5f - PApplet.map(vel.mag(), 0, maxSpeed, 0, 1);
            newForce.mult(speedFactor);
    
            displayVectors(p, d, vel, 0, 0, 255);
    
            applyForce(newForce);
    
            vel.limit(maxSpeed);
            protected abstract void force(float dt, PApplet p); 
            }    
                                        


The inspiration for the movement was source based games where when you press a desired key, you'll have your movement incrementally added up until you reach the desired direction, instead of being instantaneous. The current velocity keeps being added a new force until it reaches the new direction vector, which is calculated depending on the input of the player:



The enemy for example is an Autonomous Agent, and it's movement will be similar to the player, however the way the wished direction vector is calculated depends on the behaviour that the agent is using. In this game the boid uses 2 behaviours - wander and seek, which will be explained in greater detail in the Boid section. The following is the force function of the boid class:



            @Override
            public void force(float dt, PApplet p) {
                PVector vd = applyBehaviours(dt, p).copy(); // this line is different
                
                PVector frictionForce = PVector.mult(vel, -frictionCoeff);
                
                vel.add(frictionForce);
                pos.add(PVector.mult(vel, dt));
                vel.add(PVector.mult(vd, dt)); //here the paramater vd is different, in player it uses ac
            
                float speedFactor = 1.5f - PApplet.map(vel.mag(), 0, maxSpeed, 0, 1);
                PVector newForce = PVector.mult(vd, speed * accelerationBoost);
                newForce.mult(speedFactor);
            
                applyForce(newForce);
                vel.limit(maxSpeed);
            }
            
     

In retrospective, the common functionaly in t he force method, between the player and enemy classes, could be refactored into it's own method in the rigidBody class. Only the directional vector is calculated differently and could've been passed as a parameter, to reduce repeated code with the same functionality


Bullet

The Bullet class also implements it's own force method, since once it's been shot I wanted the bullet to move in a straight line, keeping the direction at the moment it was shot. The following code implements this:


            @Override
            protected void force(float dt, PApplet p) {
                pos.x += speed * dirx;
                pos.y += speed * diry;
        
            }

Particle System

The particle System has the same movement as the player but the particles, use the Velocity movement case of the rigidBody. And with that all entities that use the class RigidBody have it's movement implemented!

Autonomous Agent - Boid

The Boid

Boids are a form of autonomous agents that can be though of as a small robot that will respond directly to any stimuli its sensors give it without processing it. A boid can have many behaviours and through this course I implemented many but in my game I only ended up using two: Seek and Wander

The boid was used to be the fundamental "inteligence" of the enemy. The enemy will randomly explore the map through the virtual world and if it spots the player it will start seeking it. In the diagram provided it examplifies how the enemy is structured, however in this section only the boid class will be focused on. The different that combined make the enemy are:

  • Enemy class - holds the data of the enemy itself
  • Behaviour - any boid behaviour extends this class that itself implements IBehaviour
  • DNA - defines the possible values for the boid like maxSpeed and maxForce
  • Eye - class that enables the boid to "see" the world
  • RigidBody - class already explained in the previous section, it represents the body of the boid

Seek Behaviour

When the player enters the boid vision, the boids position and the player will be substracted, creating the seek vector


@Override
public PVector getDesiredVelocity(Boid me) {
    RigidBody bodyTarget = me.eye.target;
    if (me.getEye().inSight(me.eye.target.getWorldPosition(), me.dna.getVisionDistance(), me.dna.getVisionAngle()))
        return PVector.sub(bodyTarget.getScreenPosition(), me.getScreenPosition());
    else
        return new PVector();
}
                    
                    

Wander Behaviour

The wander behaviour lets the boid wander through the map when a target (the player) is not in it's vision. Given a wanderRadius, the boid "selects" a random point. Give that random point it calculates the distance between itself and that point and then goes to that point. The following diagram examplifies this:


@Override
public PVector getDesiredVelocity(Boid me) {
    PVector center = me.getWorldPosition();
    center.add(me.getVelocity().mult(me.dna.getDeltaTWander()));
    
    float x = me.dna.getRadiusWander() * (float) Math.cos(me.phiWander);
    float y = me.dna.getRadiusWander() * (float) Math.sin(me.phiWander);
    PVector target = new PVector(x,y);
    target.add(center);
    me.phiWander += 2 *  (Math.random()-0.5 ) * me.dna.getDeltaPhiWander();
    return PVector.sub(target, me.getWorldPosition());
}
                
            

Particle System

In this game I used a particle system to simulate the exhaust coming from the ship. To achieve this, after implemeting the particle system itself, I added it to the end of the ship and made sure the way the exhaust moves is the same as the player. Therefore the exhaust will always keep its relative position to the ship, the same. It was also fun to learn that William T. Reeves of LucasFilm was the creator of the particle systems, with the iconic "Genesis Effect"

To understand a particle system it's important to understand the key differences between the particles generated by the Particle System and the Particle System itself. A particle system is something that has mass and can move through the virtual space and interact with this virtual world. However the particles itself don't have mass and don't interact with the world outside the system. Therefore the particles live and die by the system. The following diagram is the uml of the particle system that was implemented. To be noted that even tho it's not in the uml, the particle system class extends the class rigidBody

Adding Particles

Depending on the frameRate more or less particles will be added. This means that for computers that are slower and therefore have lower framerates will have less particles added per frame


    private void addParticles(float dt) {
        float particlesPerFrame = psc.getFlow() * dt; // flow -> particulas por segundo
        int n = (int) particlesPerFrame;
        float f = particlesPerFrame - n;

        for (int i = 0; i < n; i++)
            addOneParticle();
        if (Math.random() < f)
            addOneParticle();
    }
    private void addOneParticle() {

        if (particles.size() < maxParticles) {

            Particle particle = new Particle(pos, psc.getRndVel(), psc.getRndRadius(),
                    psc.getRndLifetime(), psc.getColour());
            particles.add(particle);
        } else
            return;
    }

Executing the particle System

After a while the particles will be queued to be deleted. New particles are only added if the particles list has space for it


    public void run(float dt, PApplet pa) { // as par

        super.move(dt, ControlType.FORCE, pa);
        if (isMoving)
            addParticles(dt);
        for (int i = particles.size() - 1; i >= 0; i--) {
            Particle p = particles.get(i);
            p.move(dt, ControlType.VELOCITY, pa);
            if (p.isDead()) {
    
                particles.remove(i);
            }
        }
    
    }

Adding Particles

The particle itself is not very complex. Using processing graphical capabilities I decided to draw circles with random colors ranging from dark red to bright orange, to simulate fire produced by an exhaust. To note that it's possible to use sprites to represent the particles


public class Particle extends RigidBody {
    private float lifespan;
    private float timer;
    private int[] colour;

    protected Particle(PVector pos, PVector vel, int radius, float lifespan, int[] colour) {
        super(pos, vel, 0f, radius);
        this.colour = Arrays.copyOf(colour, colour.length);
        this.lifespan = lifespan;
        timer = 0;
    }

    @Override
    public void move(float dt, ControlType ct, PApplet p) {
        super.move(dt, ControlType.VELOCITY, p);
        timer += dt;
    }

    public boolean isDead() {
        return timer > lifespan;
    }

    @Override
    public void display(PApplet p) {

        p.pushStyle();
        float alpha = PApplet.map(timer, 0, lifespan, 255, 0);

        p.fill(getRnd(100, colour[0]), getRnd(10, colour[1]), colour[2], alpha);

        p.noStroke();
        p.circle(pos.x, pos.y, radius * PApplet.PI);
        p.popStyle();

    }

    public static int getRnd(int min, int max) {
        return min + (int) (Math.random() * (max - min));
    }

    @Override
    protected void force(float dt, PApplet p) {
        // TODO Auto-generated method stub
        throw new UnsupportedOperationException("Unimplemented method 'force'");
    }
    }

Cellular Automata

In a future where this game development was finished, this game would be a roguelike. I'm a very big fan of the genre and how it can provide many upon many hours of content through techniques like procedural generation. In the present, the only procedural generation in this game is the map through the use of cellular automata. This blog helped me understand the use of automata to create cave-like maps. The cells that stayed alive after n generations represent the rocks and the dead cells represent empty space that can be navigated through

The level generation system was divided in 3 parts

  • The Map that represents the generated map
  • The CellularAutomataGenerator: the generator that creates the map through cellular automata
  • The cell: The class that models an individual cell
This 3 parts are represented in the following uml (will be updated to a better img):

Development Challenges

The first problem was that I had to implement all this systems from scratch in a month, which can be overwhelming. On top of that we were required to use the Processing library with java which is not a tool designed to make games. However despite this hurdles I sucessfuly implemented the before mentioned systems

Personal Reflection

This project was useful to learn how physics can be implement in games. In particular it helped me have a better grasp around the implementation of a rigidBody which is common in many engines. It also helped me understand different systems like boids, cellular automata and particle Systems