Evolution of the EntityRenderDispatcher
When I split rendering from logic a while back, I ran into some design issues with having an efficient and flexible way of rendering different types of entities, like items, monsters and arrows. The reason for the split in the first case was to be able to easily compile a server that didn’t require opengl to run.
My first idea was to have an “Object renderer” reference in each Entity, then do something like ((EntityRenderer)entity.renderer).render(). This reference had to be set whenever the entity was added to the scene, which meant basically a bunch of if/elses to set the proper renderer to the proper entity class.
It WORKED, but there were two problems. Firstly, an if/else ladder is horrible design. It was never near being anything like a bottleneck, but it just feels bad to have an O(N) lookup every time an entity gets added. Secondly, if I wanted to render an entity NOT in the scene (for example, the player portrait in the inventory screen, or items in the player’s hand), the renderer reference would be null.
So I got rid of the renderer reference, and made a EntityRendererLookup singleton that would return the proper renderer for any given entity instance. Since the lookup now gets done every time an entity gets rendered instead of just when added to the scene, the ifs (ives?) got even worse. Still not a bottle neck, of course, but I could imagine it slowing things down if the game ever grows to having a couple of thousand different types of renderers, maybe. But it had to go, so it turned into a hashmap lookup:
public class EntityRendererLookup {
private Maprenderers = new HashMap ();
public static EntityRendererLookup instance = new EntityRendererLookup();
private EntityRendererLookup() {
renderers.put(Spider.class, new SpiderRenderer());
renderers.put(Creeper.class, new CreeperRenderer());
.. and so on ...
}
public EntityRenderer getRenderer(Class e) {
return renderers.get(e);
}
}
This worked pretty well, and is still the basis of the rendering lookup. The biggest flaw is that entities without renderers would crash the game. I could just add a bunch of “if (renderer!=null) renderer.render()” checks, but I figured it was much easier if the lookup always returned a valid renderer. So I made a quick “DefaultRenderer” that would just render a gray box, shaped as the bounding box of the entity, bound that to the Entity.class object, then made the getRenderer() class check the super class if no renderer was found for the incoming class.
By doing so, I could also make subclasses of entities, and they would get rendered as their parents if they had no specific renderer, which is nice.
Coding renderers was still a big pain, as there was a bunch of methods like render(Entity e), and you’d have to manually cast the e into the proper entity type if you wanted any specific information, like if a creeper was currently in the swelling state or not. But thankfully, there are generics for this, so it ended up looking like this:
public abstract class EntityRenderer
{
public abstract void render(T entity);
}
public class Creeper extends EntityRenderer{
public void render(Creeper creeper) {
if (creeper.isSwelling()) doStuff();
creeperModel.renderAt(creeper.getPos());
}
}
Very nice. Suddenly writing renderers got really easy, and I could stick a lot of shared stuff (rendering flames on entities, the shadows, drawing outlines) in the EntityRenderer base class.
Of course, to be able to USE this genericism, I had to add it to the lookup (which I renamed EntityRenderDispatcher
and gave a render(Entity e) method, for convenience).
This is what the current version looks like, with some
fluff trimmed out:
public class EntityRenderDispatcher {
private Map<Class<? extends Entity>, EntityRenderer<? extends Entity>> renderers = new HashMap<Class<? extends Entity>, EntityRenderer<? extends Entity>>();
public static EntityRenderDispatcher instance = new EntityRenderDispatcher();
private EntityRenderDispatcher() {
renderers.put(Spider.class, new SpiderRenderer());
renderers.put(Pig.class, new MobRenderer(new PigModel()));
renderers.put(Sheep.class, new SheepRenderer(new SheepModel(), new SheepFurModel()));
renderers.put(Creeper.class, new CreeperRenderer());
renderers.put(Skeleton.class, new MobRenderer(new SkeletonModel()));
renderers.put(Zombie.class, new MobRenderer(new ZombieModel(),));
renderers.put(Player.class, new PlayerRenderer());
renderers.put(Giant.class, new GiantMobRenderer(new ZombieModel()));
renderers.put(Mob.class, new MobRenderer(new HumanoidModel(), 0.5f));
renderers.put(Entity.class, new DefaultRenderer());
renderers.put(Painting.class, new PaintingRenderer());
renderers.put(Arrow.class, new ArrowRenderer());
renderers.put(ItemEntity.class, new ItemRenderer());
renderers.put(PrimedTnt.class, new TntRenderer());
}
publicEntityRenderer getRenderer(Class<? extends Entity> e) {
EntityRenderer<? extends Entity> r = renderers.get(e);
if (r == null && e != Entity.class) {
r = getRenderer((Class<? extends Entity>)e.getSuperclass());
renderers.put(e, r);
}
return (EntityRenderer) r;
}
publicEntityRenderer getRenderer(Entity e) {
return getRenderer(e.getClass());
}
public void render(Entity entity) {
getRenderer(entity).render(entity);
}
}
It’s very nice to work with, with the exception that every time I add a new renderer, I have to add it to the constructor in this class, or it won’t get used. But I can live with that.