Observer Pattern

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

Observer Pattern

Observer Nodes | by The Corda Team | Corda | Medium

Observer is a behavioral design pattern that lets you define a subscription mechanism to notify multiple objects about any events that happen to the object they’re observing.

In games but also programming applications in general we have many observable subjects. But we don’t want to have direct access to full objects for a “simple” notification.
This is when Observer Pattern comes to play!
Subjects can determine which observers they want to notify. They can do them all, or a set of observers. But shouldn’t really care who all the observers are.

Observer patter in nowadays so common that most languages have integrated it in their native language! C# for example has baked it with the event keyword!

Examples

Musicians

House of Heats
DubFX perfomancing

A musician loves a big crowd. But he/she does not care about who is actually watching (observing) them, but they do notify the crowd once it’s time to cheer/clap for their performance. This way every time a musician finishes a song they call an event to all their observers and they can then decide to cheer, clap, scream WE WANT MORE or what ever. πŸ˜‰

Auction
When being in a action everyone can observe the Auctioneer! The auctioneer is the subject which broadcasts to all observers a new “highest bid”. Once the highest bid is claimed, all observers will be notified that the auction is closed.

Intentions of using Observer Pattern

  • Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Demo

Demo introduction

In this demo we will make observers to listen to our bullets and missiles if they are hitting each other or not.
To make the bullets feel more like the bullets as in the original missile command, we also scale each bullet in and out!

I choose to not use Java native observer pattern to highlight its usage in a simple way.

In steps

  • Creating observers and subjects
  • Applying subject and observers
  • Register bullet observers
    • When bullet reached position we scale
    • While scaling we check collision
    • When bullet dies (hits scale 0) we remove bullet

Code

Note: In some examples, 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!
All interfaces we will add will start with I.

Observers and Subject

For this demo we need to create a subject class to notify the observers.

IObserver.java

This is our main observer, only used for identification that we are storing observers. All the other upcoming observers will inherent from this.

package observers;

public interface IObserver {
	// only for identification for subjects
}
IDieObserver.java

We will use the die observer when a bullet “dies” so we can remove it.

package observers;

public interface IDieObserver extends IObserver {
	// when subject dies, it fires onDie method to
	// all observers
	public <T> void onDie(Class<T> c);
}
IMoveToPositionObserver.java

Whenever a bullet has reached it’s position, we will start scaling.

package observers;

public interface IMoveToPositionObserver extends IObserver{
	// when subject reaches position, it fires onDie method to
	// all observers
	public void onPosition();
}
IScaleObserver.java

When bullet is scaling, we will check collision with missile mediator!

package observers;

import components.Image;

public interface IScaleObserver extends IObserver {
	// when subject scaled, it fires onScale method to
	// all observers passing the image for subjects to check
	// the image data.
	public void onScale(Image image);
}

Subject.java

This subject class is similar to our entity class which we developed in ECS episode! It stores all observers

package subject;

import java.util.ArrayList;
import java.util.List;
import observers.IObserver;

public class Subject {
	
	protected List<IObserver> observers = new ArrayList<>();

	// adding observers.
	public void register(IObserver observer) {
		observers.add(observer);
	}

	// remove observer
	public void unregister(IObserver observer) {
		if(!observers.contains(observer)) {
            throw new IllegalArgumentException("This subject does not contain this observer");
		}
		
		observers.remove(observer);
	}
	
	// each subject can decide what observer it wants
	// to notify. Therefor they can call this method
	// to request all observers of a certain type.
	public<T> List<T> getObservers(Class<T> c){
		List<T> result = new ArrayList<>();
		
		for (int i = 0; i < observers.size(); i++) {
			IObserver observer = observers.get(i);
			if(c.isInstance(observer)) {
				result.add(c.cast(observer));
			}
		}
		
		return result;
	}
}
Component.java

Our components will be our subjects. Each component can we register an observer interface which each component sub class will fire their methods when needed.

package ecs;

...

import subject.Subject;

public abstract class Component extends Subject {

	...
}

Subject components

MoveToPosition.java
package components;

...
import observers.IMoveToPositionObserver;

public class MoveToPosition extends Component {

	...

	@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;
			
			// firing on position to notify all our observers.
			onPosition();
		}
	}

	...
	
	private void onPosition() {
		// notifying all our observers!
		for (IMoveToPositionObserver observer : getObservers(IMoveToPositionObserver.class)) {
			observer.onPosition();
		}
	}
}
ImageScaler.java

This is a new component we will use on our bullet to scale up and down our bullet image.

package components;

import java.awt.Graphics2D;

import ecs.Component;
import observers.IDieObserver;
import observers.IScaleObserver;

public class ImageScaler extends Component {

	private Image image;
	private float maxScale;
	private float scaleStep;
	private float delay;
	private boolean invertScale;
	
	private float time = 0;
	private float currentScale;
	
	private boolean died = false;
	
	public ImageScaler(float maxScale, float scaleStep, float delay, boolean invertScale) {
		this.maxScale = maxScale;
		this.scaleStep = scaleStep;
		this.delay = delay;
		this.invertScale = invertScale;
	}
	
	@Override
	public void init() {
		image = entity.getComponent(Image.class);
		currentScale = image.getScale();
	}

	@Override
	public void update(double deltaTime) {
		if(died) {
			return;
		}
		
		time += deltaTime;
		
		if(time < delay) {
			return;
		}
		time = 0;
		
		currentScale += scaleStep;
		
		if(currentScale > maxScale) {
			currentScale = maxScale;
			if(invertScale) {
				scaleStep *= -1;
			} else {
				onDie();
			}
		} else if(currentScale < 0) {
			currentScale = 0;
			
			onDie();
		}
		
		image.setScale(currentScale);
		onScaling();
		
	}

	@Override
	public void render(Graphics2D g) {
	}
	
	private void onDie() {
		died = true;
		for (IDieObserver observer : getObservers(IDieObserver.class)) {
			observer.onDie(entity.getClass());
		}
	}
	
	private void onScaling() {
		for (IScaleObserver observer : getObservers(IScaleObserver.class)) {
			observer.onScale(image);
		}
	}
}

Our scaling effect

Registering observers

Bullet.java
package bullet;


import components.Image;
import components.ImageScaler;
import components.MoveToPosition;
import components.Transform;
import ecs.Entity;
import math.Vector2;
import observers.IDieObserver;
import observers.IMoveToPositionObserver;
import observers.IScaleObserver;
import sprite.Sprites;

public class Bullet extends Entity implements IMoveToPositionObserver {

	private static final float speed = 2f;
	
	// adding scaling data
	private static final float maxScale = 7.5f;
	private static final float scaleStep = .5f;
	private static final float delay = 10f;
	private static final boolean invertScale = true;
	
	private IScaleObserver scaleObserver;
	private IDieObserver dieObserver;

	public Bullet(float x, float y, Vector2 target) {
		addComponent(new Transform(x, y));
		addComponent(new Image(Sprites.instance.getBullet()));
		
		MoveToPosition mtp = new MoveToPosition(target, speed);
		// registering this object because we are listening to when we moved
		// our target position!
		mtp.register(this);
		addComponent(mtp);
	}
	
	// add scale observers to our scaler
	// can not do it directly because we scale
	// after we hit our target position
	public void addScaleObserver(IScaleObserver observer) {
		this.scaleObserver = observer;
	}
	
	// add die observer which will be added once we start scaling
	public void addDieObserver(IDieObserver observer) {
		this.dieObserver = observer;
	}

	@Override
	public void onPosition() {
		// removing component because it's not needed anymore.
		removeComponent(MoveToPosition.class, false);
		
		ImageScaler scaler = new ImageScaler(maxScale, scaleStep, delay, invertScale);
		// register cached observers
		scaler.register(scaleObserver);
		scaler.register(dieObserver);
		addComponent(scaler);
	}
}
BulletMediator.java
package bullet;

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

import components.Image;
import missile.MissileMediator;
import observers.IDieObserver;
import observers.IScaleObserver;

public class BulletMediator implements IScaleObserver, IDieObserver {
	
	// all bullets stored
	private List<Bullet> bullets = new ArrayList<>();
	
	private MissileMediator missileMediator;
	
	public BulletMediator(MissileMediator missileMediator) {
		this.missileMediator = missileMediator;
	}
	
	// adding, updating, rendering and disposing
	// bullets. Must be very much self explaining :)
	public void addBullet(Bullet bullet) {
		bullets.add(bullet);
		bullet.addScaleObserver(this);
		bullet.addDieObserver(this);
	}
	
	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();
	}

	@Override
	public void onScale(Image image) {
		missileMediator.checkCollision(image);
	}

	@Override
	public <T> void onDie(Class<T> c) {
		Bullet removeBullet = null;
		removeBullet =  (Bullet) c.cast(removeBullet);
		
		if(removeBullet == null) {
			return;
		}
		
		bullets.remove(removeBullet);
	}
}

Collision detection

MissileMediator.java
package missile;

...

public class MissileMediator {

	...

	public void checkCollision(Image image) {
		for (int i = 0; i < missiles.size(); i++) {
			Missile missile = missiles.get(i);
			if(missile.collisionDetection(image)) {
				// missile is hit! DIE!
				System.out.println("MISSILE HIT");
				
				// removing missile because we don't need it anymore. It's destroyed!
				missiles.remove(missile);
				i--;
			}
		}
	}
}

This really highlights the previous episode about mediator pattern!
Instead of letting each bullet have individual reference to each missile to check collision detection, we let the mediators check with each other.
That makes we only have dependency with the two mediators. πŸ˜‰
Note: This is not optimised.

Source code – GitHub

You can get all source codes here!

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

Breakdown

We now have a playable game! πŸŽ‰
We can finally destroy our missiles by observing our bullets. But also added lot’s of opportunities with this pattern. We can easily register observers to any subject, which is really nice for our scalability.

But also try to be careful. Don’t overuse this pattern too much, because it will be quite hard to debug at some point. So, try to keep it clean so you have a clear overview as a developer of who’s observing who πŸ˜‰. Because if you lose the overview, well.. good luck debugging it!

Resources

Derek Banas

Game Programming Patterns

Game Programming Patterns – Book Review

Game Programming Patterns highlighting the observer pattern greatly with a achievements system!
https://gameprogrammingpatterns.com/observer.html

Source Making

SourceMaking

Great and simple explanation of the observer pattern, but also it’s problem.
https://sourcemaking.com/design_patterns/observer


Hope you enjoyed this episode!
Have you ever used this pattern before? Let me know!

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

Your email address will not be published. Required fields are marked *