Mediator pattern

Welcome to part 6 of the Design Patterns series. This episode will be about the Mediator Pattern!

Mediator Pattern

This busy street intersection with no traffic lights is just pure chaos ..  - GIF on Imgur

Mediator is a behavioral design pattern that lets you reduce chaotic dependencies between objects. The pattern restricts direct communications between the objects and forces them to collaborate only via a mediator object.

https://refactoring.guru/design-patterns/mediator

In games once you start to have multiple entities flowing around your scene, it starts to become pretty chaotic. All entities will have to watch each other and manipulate each other causing a lot of dependencies.
This is when mediators start to be a perfect fit.

Examples

Traffic Lights
Just like in real life, the public roads are a chaotic mess once there’s no traffic light. Even though there are still rules, not everyone listens to them or forgot to pay attention. This causes a lot of blockages, accidents, annoyance and headaches.
A traffic light is a great mediation to tackle these problems. It tells each individual what to do and manages them all. Also does it have some logic to keep the traffic flow as efficient as possible.

ATC tower

Grote verkeerstoren Schiphol bestaat 25 jaar | Het Parool

Every airport has an ATC (Air Traffic Controller) tower mediating each vehicle to tell whether it can land or not. But also when it can depart or has to be delayed.
It’s the manager of all aircraft, big to small and planes to choppers.

Intentions of using Mediator Pattern

  • Handling communication between related objects

Demo

Demo introduction

For this demo we will add bullets to our turrets!
But we will also create mediators for our missiles and turrets.
The turret mediator will communicate with the bullet mediator in order to fire. But the bullet mediator will communicate back with the missile mediator to decide what to do when they hit each other! But more about that next episode πŸ˜‰ because we really need observers for that.

Code

Disclaimer: The dots indicating code which is written in previous episode to highlight what’s new. If wanting to have full code, check out previous episode or GitHub link below!

Bullets

MoveToPosition.java

For this demo, we want the bullets to move to the position we click. So, to do this, we must have a new component which moves the entity (bullet) to a defined position!

package components;

import java.awt.Graphics2D;

import ecs.Component;
import math.Vector2;

public class MoveToPosition extends Component {

	// cached component
	private Transform transform;
	// target to move to
	private Vector2 target;
	// speed till reached target
	private float speed;
	
	public MoveToPosition(float x, float y, float speed) {
		target = new Vector2(x, y);
		this.speed = speed;
	}
	
	public MoveToPosition(Vector2 target, float speed) {
		this.target = target;
		this.speed = speed;
	}
	
	@Override
	public void init() {
		transform = entity.getComponent(Transform.class);
	}

	@Override
	public void update(double deltaTime) {
		// moving towards the direction we calculate in Vector class
		Vector2 direction = transform.position.direction(target);
		// adding position
		transform.position.x += direction.x * speed * deltaTime;
		transform.position.y += direction.y * speed * deltaTime;
		
		if(Vector2.getDistanceD(transform.position, target) <= 1.5f) {
			// checking distance to clamp our position
			// otherwise it will keep shaking on it's position.
			// the amount of shake will vary on the speed you choose!
			transform.position = target;
		}
	}

	@Override
	public void render(Graphics2D g) {
	}
	
}
Bullet.java

The actual bullet!
It’ll just be a white dot.

package bullet;

import components.Image;
import components.MoveToPosition;
import components.Transform;
import ecs.Entity;
import math.Vector2;
import sprite.Sprites;

public class Bullet extends Entity {
	
	private static final float speed = 2f;

	public Bullet(float x, float y, Vector2 target) {
		addComponent(new Transform(x, y));
		addComponent(new Image(Sprites.instance.getBullet()));
		addComponent(new MoveToPosition(target, speed));
	}
}

This really shines the ECS from two episodes again doesn’t it?
Such a simple class for a fun and big addition to our game!

BulletMediator.java
package bullet;

import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;

import missile.MissileMediator;
import turret.TurretMediator;

public class BulletMediator {
	
	// all bullets stored
	private List<Bullet> bullets = new ArrayList<>();
	
	private MissileMediator missileMediator;
	
	public BulletMediator(MissileMediator missileMediator) {
		// caching missile mediator for later use ;)
		this.missileMediator = missileMediator;
	}
	
	// adding, updating, rendering and disposing
	// bullets. Must be very much self explaining :)
	public void addBullet(Bullet bullet) {
		bullets.add(bullet);
	}
	
	public void update(double deltaTime) {
		for (int i = 0; i < bullets.size(); i++) {
			Bullet bullet = bullets.get(i);
			bullet.update(deltaTime);
		}
	}
	
	public void render(Graphics2D g) {
		for (int i = 0; i < bullets.size(); i++) {
			Bullet bullet = bullets.get(i);
			bullet.render(g);
		}
	}
	
	public void dispose() {
		for (int i = 0; i < bullets.size(); i++) {
			Bullet bullet = bullets.get(i);
			bullet.dispose();
		}
		
		bullets.clear();
	}
}

Our first mediator! Even though it’s just a manager storing, updating and managing our bullets.

Let’s fire our bullets!

TurretBarrel.java

Of course we will shoot from our barrel!

package turret;

...

public class TurretBarrel extends Entity {
  
    ...
	
	public Bullet shoot(Vector2 target) {
		Vector2 position = getComponent(Transform.class).position;
		// get direction from position towards target
		Vector2 direction = position.direction(target);
		
		// add direction so it's aligned with the barrel.
		// * 5 to make it start lower
		float x = position.x + direction.x * 5;
		float y = position.y + direction.y * 5;
		
		return new Bullet(x, y, target);
	}
  
    ...
	
}
TurretBase.java

Just a very minor adjustment. Since we store TurretBase to access from outer resources, we will call barrel to shoot here.

package turret;

...

public abstract class TurretBase extends Entity {
	
	protected TurretBarrel turretBarrel;
	
	...
	
    // shooting the barrel!
	public Bullet shoot(Vector2 target) {
		return turretBarrel.shoot(target);
	}
	
	...
      
}
TurretMediator.java
package turret;

import java.awt.Graphics2D;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.util.ArrayList;
import java.util.List;

import bullet.BulletMediator;
import components.Transform;
import main.GameWindow;
import math.Vector2;

// listening to mouse to shoot our turrets!
public class TurretMediator implements MouseListener{
	
	private static final int turretCount = 10;
	
	private List<TurretBase> turrets = new ArrayList<>();
	
	private BulletMediator bulletMediator;
	
	public TurretMediator(BulletMediator bulletMediator) {
		// caching bullet mediator which will be used to fire our bullet!
		this.bulletMediator = bulletMediator;
		
		// moved turret creation from gameloop here!
		TurretFactory turretFactory = new TurretFactory();
		for (int i = 0; i < turretCount; i++) {
			float x = ((800 / turretCount) / 2) + i * (800 / turretCount);
			turrets.add(turretFactory.getTurret(x, 500, i, turretCount));
		}
		
		GameWindow.instance.addMouseListener(this);
	}
	
	// shooting turret towards mouse position!
	public void shootTurret(int x, int y) {
		// shooting position, used to get distance
		Vector2 shootPosition = new Vector2(x, y);
		// caching closest turret
		TurretBase closestTurret = null;
		// having a really high number so it will loop through all turrets.
		// when lower number is found it will cache that distance and found turret until
		// a new closer distance towards position is found.
		double closestDistance = 99999999;
		for (TurretBase turretBase : turrets) {
			double distance = Vector2.getDistanceD(turretBase.getComponent(Transform.class).position, shootPosition);
			if(distance < closestDistance) {
				closestDistance = distance;
				closestTurret = turretBase;
			}
		}
		
		// Shoot from turret closest to our mouse position!
		bulletMediator.addBullet(closestTurret.shoot(shootPosition));
	}
	
	public void update(double deltaTime) {
		for (TurretBase turret : turrets) {
			turret.update(deltaTime);
		}
	}
	
	public void render(Graphics2D g) {
		for (TurretBase turret : turrets) {
			turret.render(g);
		}
	}
	
	public void dispose() {
		GameWindow.instance.removeMouseListener(this);

		for (TurretBase turret : turrets) {
			turret.dispose();
		}
		
		turrets.clear();
	}

	@Override
	public void mousePressed(MouseEvent e) {
		// - 32 because of top bar size
		shootTurret(e.getX(), e.getY() - 32);
	}

	@Override
	public void mouseClicked(MouseEvent e) {
	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

	@Override
	public void mouseReleased(MouseEvent e) {
	}
}

Missile mediator and Play state update

Now we have a mediator managing our bullets and turret, it’s best to also add a missile mediator to switch dependencies. It will be used in its full potential next episode when using observer pattern!

MissileMediator.java

We basically move all logic from our play state about missiles to our mediator.

package missile;

import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import bullet.BulletMediator;

// moved all logic about missiles from gameloop to this mediator.
public class MissileMediator {

	private static final int normalMissilesCount = 2;
	private static final int fastMissilesCount = 0;
	private static final int slowMissilesCount = 0;
	private static final int randomMissilesCount = 0;
	
	private static final boolean loopCreations = true;

	private List<Missile> missiles = new ArrayList<>();
	private List<Missile> keepMissilesAlive = new ArrayList<>();
	
	private MissileFactory missileFactory;
	private Random random;

	private float randomX() {
		return 10 + random.nextFloat() * (750);
	}
	
	private float randomY() {
		return random.nextFloat() * (-500);
	}
	
	public MissileMediator() {
		missileFactory = new MissileFactory(normalMissilesCount, fastMissilesCount, slowMissilesCount);
		random = new Random();
		
		int size = normalMissilesCount + fastMissilesCount + slowMissilesCount + randomMissilesCount;
		for (int i = 0; i < size; i++) {
			float x = randomX();
			float y = randomY();

			missiles.add(missileFactory.getMissile(x, y, i));
		}
	}
	
	public void update(double deltaTime) {
		List<Missile> removeMissiles = new ArrayList<Missile>();
		
		for (Missile missile : missiles) {
			missile.update(deltaTime);

			if(missile.isStopped()) {
				removeMissiles.add(missile);
			}
		}
		
		loopCreations(removeMissiles);
	}
	
	private void loopCreations(List<Missile> removeMissiles) {
		if(!loopCreations) {
			return;
		}
		
		if(removeMissiles.size() > 0) {
			missiles.removeAll(removeMissiles);
			keepMissilesAlive.addAll(removeMissiles);
			for (int i = 0; i < removeMissiles.size(); i++) {
				missiles.add(missileFactory.getMissile(randomX(), randomY(), removeMissiles.get(i).getCreationType()));
			}
		}
		
		while (keepMissilesAlive.size() > 15) {
			keepMissilesAlive.remove(0); // removing missile when it reached a limit for performance
		}
	}
	
	public void render(Graphics2D g) {
		for (Missile missile : missiles) {
			missile.render(g);
		}
		
		for (Missile missile : keepMissilesAlive) {
			missile.render(g);
		}
	}
	
	public void dispose() {
		for (Missile missile : missiles) {
			missile.dispose();
		}

		for (Missile missile : keepMissilesAlive) {
			missile.dispose();
		}
		
		missiles.clear();
		keepMissilesAlive.clear();
	}
}
PlayState.java

Now we have our mediators, we can apply them in our play state!

package gamestate.states;

import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import javax.swing.AbstractAction;

import bullet.BulletMediator;
import gamestate.GameState;
import gamestate.GameStateManager;
import missile.Missile;
import missile.MissileFactory;
import missile.MissileMediator;
import sprite.Sprites;
import turret.TurretBase;
import turret.TurretFactory;
import turret.TurretMediator;
import ui.Button;

public class PlayState extends GameState {
	private Button backBtn;

	private TurretMediator turretMediator;
	private BulletMediator bulletMediator;
	private MissileMediator missileMediator;
	
	private boolean initialized = false;

	public PlayState(GameStateManager gsm) {
		super(gsm);
	}

	@Override
	public void init() {
		missileMediator = new MissileMediator();
		bulletMediator = new BulletMediator(missileMediator);
		turretMediator = new TurretMediator(bulletMediator);
		
		backBtn = new Button(100, 50,
				Sprites.instance.getBtnBackNormal(), Sprites.instance.getBtnBackHover(), Sprites.instance.getBtnBackPressed());
		backBtn.setScale(0.5f);
		
		backBtn.setOnClickAction(new OpenMenuState());
		
		initialized = true;
	}

	// internal class to handle button action. Java doesn't allow lambda expressions as
  	// well known in C#, so this is a work-around option ;)
	private class OpenMenuState extends AbstractAction {

		@Override
		public void actionPerformed(ActionEvent e) {
			gsm.setState(new MenuState(gsm));
		}
	}

	@Override
	public void update(double deltaTime) {
		if(!initialized) {
			return;
		}
		
		turretMediator.update(deltaTime);
		missileMediator.update(deltaTime);
		bulletMediator.update(deltaTime);
		
		if(backBtn != null) {
			backBtn.update(deltaTime);
		}
	}

	@Override
	public void render(Graphics2D g) {
		if(!initialized) {
			return;
		}
		
		turretMediator.render(g);
		missileMediator.render(g);
		bulletMediator.render(g);

		if(backBtn != null) {
			backBtn.render(g);
		}
	}

	@Override
	public void dispose() {
		backBtn.dispose();
		
		turretMediator.dispose();
		missileMediator.dispose();
		bulletMediator.dispose();
	}

}

We now cleaned up 61 lines of codes by only adding mediators. Which makes our play state much easier to maintain and to look at.

Source code – GitHub

You can get all source codes here!

https://github.com/jscotty/DesignPatterns/tree/MediatorPattern

Breakdown

Just like in real life, sometimes we need a mediator to tell us what to do. It’s like a manager. It will communicate with other mediators to come to solutions, as we now done with our turrets and bullets.
Because if we would let them handle them all individually, it would be a mess to handle.

That’s why mediation is so great. It takes away a lot of individual dependencies which if you would create a UML diagram, give you a big headache! πŸ˜…

Comparison

As I complained last episode that we gained to much code by creating states, this mediator pattern was a great solution to solve it.
We separated object creation and data caching to each mediator. They are responsible for the actions of their data, not our play state!

Resources

Derek Banas

It wouldn’t be another resources section without Derek Banas, wouldn’t it? 😜

Refactoring Guru

Refactoring and Design Patterns

Refactoring guru has once again a great explanation about the mediator pattern. Highly recommend to check it out!
https://refactoring.guru/design-patterns/mediator

Code Pumpkin

https://codepumpkin.com/mediator-design-pattern/


Hope you enjoyed this episode!

If any questions, feel free to message me on Instagram @justinbieshaar or in comments below!

Happy coding everyone! πŸ‘¨β€πŸ’»

Greetings,
Justin Scott

Follow Justin Scott:

I love to learn and share. - 01001010 01010011 01000010

Latest posts from

Leave a Reply to Anonymous Cancel reply

Your email address will not be published.