Code Background

An MVC Guide for libGDX

Recently I was answering questions on libGDX on StackExchange, and I got a really interesting question about using the Model-View-Controller (MVC) design pattern. I’ve been experimenting with it in one of my own games recently, so I thought I would write a brief guide on how to employ it (specifically within the libGDX framework). By the end of this post, you will have learned:

  • What MVC is
  • Why MVC is awesome
  • How to use MVC (specifically with libGDX)

So what is MVC?

Model-View-Controller (MVC) is a popular way of organizing code where we split our program into three logical pieces: 1

  1. The Model is the code that represents our game logic and data
  2. The View is the code which renders the model (or a portion of the model) on the screen and collects the player’s input
  3. The Controller is the routing code which manipulates the model and stitches things together

Each of these three parts relate to each other in specific ways:

  1. The Model is read by the View and updated by the Controller.
  2. The View reads the Model and provides input to the Controller.
  3. The Controller determines which View to show and owns all updates to the Model.

This can all be summed up by the following picture:

MVC Diagram
No post is complete without a diagram

MVC in Motion: An Example

Let’s pretend we were building a PAC-MAN clone and structuring it using MVC:

First, the player opens our game. libGDX launches our Controller, which tells the View to display a menu screen. The View renders this screen and binds the “New Game” button to the Controller.

Next, the player clicks the “New Game” button. The View notifies the controller per the binding that occured earlier. The Controller instructs the Model to configure itself for a new game and changes the View to be the “Maze” screen. The new View queries the model to figure out what it should paint on the screen.

Going forward, our controller will then update the model each frame with the amount of time that has transpired and an abstract representation of the player’s input. The model would update the position of PAC-MAN and the ghosts based on this information. Finally, the View would query the model and redraw the screen with PAC-MAN and the ghosts at their new positions.

A Disclaimer

Now at this point I should mention that this is my definition of MVC. I don’t want you to feel swindled if you find that your school or workplace has a different one. The truth is that for all its popularity, there are a lot of nuances to MVC that folks don’t seem to agree on. Wikipedia, Microsoft, Apple, and CodingHorror all definite it slightly differently, and that’s just scratching the surface. If you’re not careful you can lose an afternoon reading different blogs explaining what MVC is and why the other blogs are wrong. At the end of the day, just remember the wise words of one our favorite pirate captains:

MVC is more of a guideline
He’s totally referring to design patterns

I recommend you treat MVC more like a guideline than a rule- if you use it to structure your code, there are going to be a bunch of cool things you can do (as we’ll talk about next). That said, it’s not a silver bullet. 2 You’re going to find in some cases having the model push events to the current view is a lot cleaner than trying to have the view poll the model. Or perhaps you’re going to be stuck with a legacy code base or 3rd party library which doesn’t give a rip about MVC.

Why MVC is Awesome

Even if it’s not a silver bullet, MVC is still pretty cool for several reasons:

Models Let us Test

Testing sounds boring- isn’t that what play-testers are for? But if your game has any complex algorithms (such as the AI uses to determine if the player’s trade is “fair” or not), it really helps to have unit tests you can run against that algorithm to make sure it works as expected. If someone tweaks the algorithm, you’ll know if it breaks the game immediately instead of a week later.

This sort of unit testing is difficult to do if you’ve mixed your game logic into the View or Controller because in order to create a lot of the libGDX objects, you have to initialize the OpenGL context. This makes your tests take a lot longer, and when they break you’re never sure if what broke was the portion of your code you were trying to test or not. There are tricks you can use such as “Headless” contexts, but these still add to the testing overhead.

If however all your game logic is in the model, and your model doesn’t have any of the graphics-related View code in it, you can spin up a model independent of libGDX and run your unit tests against it. This is a lifesaver in medium-to-large games.

Views let us Port

While libGDX promises (and delivers) to let your port your game between Desktop, Android, iOS, and Web platforms, that doesn’t always mean your UI will port nicely. Console games are built for game controllers with those funny little knobs. Most desktop games expect little mouse cursors instead of fat fingers. Often it makes sense to provide a different UI layout based on the platform.

If you mix your View code with your Model or Controller, adding a new layout likely means refactoring across the entire code base. If we’ve isolated our View code from everything else, then the chances of us breaking things during a port are much lower.

Controllers let us Expand

Since the Controller decouples model updates from the View, it lays the foundation for use cases where you might remove the View altogether. In addition to writing unit tests against the controller (as mentioned earlier), you can also start writing AI and network clients that interact with your game through the same interface as your view.

MVC in libGDX

Enough theory! Let’s see how this works in libGDX.

libGDX Models

When I create a model in libGDX, I try to separate it into two parts:

Game Data

This is where your game-specific stateful information goes. One really important part of MVC is making sure the stateful information is captured inside the Model and doesn’t leak out into the View or Controller, because if you keep it inside the model, saving and loading your game becomes as simple as (de)serializing your model. Due to the nature of Game Data, these classes will almost exclusively be your own, aside from some of the libGDX utility classes or datastructures (Array, ObjectIntMap).

Since the model owns the Game Data, it is responsible for owning the interface used by the view to query data and the interface used by the controller to make changes to the model. This means being able to interpret the controller’s commands.

It’s also important to note that the Game Data classes are not Plain Old Java Objects (POJOs)- they contain game logic as well. If I build a MerchantNPC class, it is responsible for storing information about the merchant (e.g. what items it has for sale) as well as the algorithm it uses to determine the price. Naturally you can use good OOP to decompose something like this into several smaller classes, but my point is that they would all fall under the Model umbrella.

Game Resources

libGDX already provides an excellent AssetManager class for loading resources such as meshes and textures, and it doesn’t make sense to rebuild this information. However, I do try to separate the “complex” libGDX objects such as Textures from my Game Data. For example, if an NPC needs a portrait, my NPC Game Data class will contain a string with the portrait name instead of a reference to the specific texture. This is probably the most contentious thing I am recommending, but it comes with some really cool benefits:

  1. It becomes a lot easier to serialize the Game Data. You can serialize strings with little to no effort; if your Game Data class contains textures, you have to add extra logic during (de)serialization to teach your game how to serialize the texture reference and then re-attach it later when you deserialize.
  2. It becomes easier to test in an environment where you don’t have an OpenGL context to do rendering (the main example being a server or unit tests). Textures come with hidden dependencies that we don’t want to mess around with.
  3. The Model doesn’t know anything about how your game world is rendered. Changing textures becomes as simple as changing a string in a JSON file.
  4. In theory 3your model becomes decoupled from libGDX and it becomes a lot easier to port it to a different Java-based library should the need arise.

Example Model

public class Model {
    private Galaxy galaxy;
    private Player player;
    private boolean active = false;

    /**
     * Start a new game.
     */
    public void startNewGame(Controller controller) { // TODO: Clean up magic screens
        this.galaxy = GalaxyFactory.get().make();
        this.player = new Player(new ObjectIntMap<>(), galaxy.getStations().get("Homeworld"), galaxy.getStations().get("Homeworld"));
        this.player.getQuests().add(new QuestFactory().make("INTRO_QUEST", controller, this));
        this.active = true;
    }

    public void dispose() { }

    // === Getters / Setters === //
    public Player getPlayer() {
        return player;
    }

    public Galaxy getGalaxy() { return galaxy; }

    public boolean isActive() {
        return active;
    }
}

libGDX Views

I use the Screen interface from libGDX as the basis for my views. Common setup goes into an abstract View class, and then child classes build the specific layouts required. Each View gets a reference to the Model (for reading what to draw) and the Controller (for making changes to the model or doing things). Beyond that, there are a few interesting things to note:

Views are Stateless

Views don’t contain any stateful information. They get state from the Model (or in a few cases like checking to see if “debug mode” is enabled, the Controller). This has a bunch of benefits:

  1. We can load/save the game by de/serializing the model as discussed earlier. If stateful information was in the View, we’d have to serialize it as well.
  2. We can create/destroy views safely without changing anything in our game. I’ve found this helps avoid a lot of weird bugs.
  3. We don’t accidentally make our Model or Controller dependent on the View to work; this allows us to create unit tests without mocking a view.

The implication of stateless views is that we can now think of them more like visual filters- their sole purpose is to render part of the model.

View Lifecycle

Each of my views/screens follows a certain lifecycle:

First, the constructor builds any static content (i.e. content that isn’t dependent on the model). This is typically done once per type of screen. After all, if views are just visual filters pointed at the same model, it doesn’t make sense to have more than one of the same type.

The show() method (re)builds any content that changes based on the model state. Examples would be labels that change based on player location, or buttons which are only shown at certain points in the game. If your screen isn’t doing any complex rendering (e.g. a menu screen or a save/load screen), it’s quite easy to use a single Stage for the UI that is rendered by the abstract View class- in these cases the implementation class only needs the constructor and show().

When hiding a button in a Scene2D.UI table-based layout, I’ve found it easier to simply rebuild the layout without the button instead of trying to change its visibility (which leaves an ugly gap) or dynamically add/remove it. While this seems efficient, it’s simple, effective, and executed once per screen transition (as opposed to once per frame, when efficiency is very important).

render() reads the model data and paints the view’s portion on the screen. The view is responsible for knowing what it needs to draw.

Example Views

An truncated example of the base screen and actual game screen can be seen below:

public abstract class BaseScreen implements Screen {
	protected GameController controller;
  	protected GameWorld world;
	protected Stage ui;
	
	public BaseScreen(GameController controller, GameWorld world) {
		this.controller = controller;
		this.world = world;
		ui = new Stage(new ScreenViewport());
	}

	// === Lifecycle Methods === //
	@Override
	final public void show() {
		// Set Debug Mode
		ui.setDebugAll(controller.isDebugOn());

		// Map the controller
		InputMultiplexer input = new InputMultiplexer();
		input.addProcessor(ui);

		// Add an input processor to toggle debug mode via F3.
		input.addProcessor(new DebugProcessor(ui, controller));
		Gdx.input.setInputProcessor(input);

		// Screen-specific initialization
		init();
	}

	public void init() { }
	
	@Override
	final public void render(float delta) {
		Gdx.gl.glClearColor(0, 0, 0, 1);
    Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
		draw(delta);
		if(ui != null) {
			ui.act(delta);
			ui.draw();
		}
	}

	/**
	 * Override this sucker to implement any custom drawing
	 * @param delta The number of seconds that have passed since the last frame.
	 */
	public void draw(float delta) {}

	@Override public void resize(int width, int height) {
		ui.getViewport().update(width, height);
	}

	@Override public void dispose() {
		if(ui != null) ui.dispose();
		ui = null;
	}
}
public class CargoScreen extends View {
	private VisList<ItemEntry> lstItems;
	private VisImage imgItem;
	private VisTextArea txtDescription;
  private VisTextButton btnBack;

	public CargoScreen(Controller parent, Model model) {
		super(parent, model);

    // Initialize the image
    imgItem = new VisImage();
    imgItem.setSize(ui.getWidth() / 2, ui.getWidth() / 2); // TODO: Refactor to world size.

    // Create the item list
    lstItems = new VisList<>();
    lstItems.addListener(new ChangeListener() {
        @Override
        public void changed(ChangeEvent event, Actor actor) {
            updateSelection(lstItems.getSelected());
        }
    });

    // Create the description field
    txtDescription = new VisTextArea();
    txtDescription.setDisabled(true);

    // Add a "back" button
    btnBack = makeNavButton("Back", StationScreen.class);

    // Create the layout
    VisTable tblLayout = new VisTable();
    tblLayout.columnDefaults(0).pad(10f);
    tblLayout.columnDefaults(1).pad(10f);
    tblLayout.setFillParent(true);
    tblLayout.add(imgItem).size(ui.getWidth() / 2);
    tblLayout.add(new VisScrollPane(lstItems)).fillY();
    tblLayout.row();
    tblLayout.add(txtDescription).size(ui.getWidth() / 2, 100.0f);
    tblLayout.add(btnBack).expandX().fillX();

    ui.addActor(tblLayout);
	}

	@Override
	public void init() {
    // Refresh the player data
		Array<ItemEntry> items = new Array<>();
		world.getPlayer().getInventory().forEach(i -> items.add(new ItemEntry(i.key, i.value)));
		items.sort(new ItemEntry.ItemEntryComparator());
		lstItems.setItems(items);
		updateSelection(lstItems.getSelected());
	}

  /**
   * Flush all stateful data.
   */
	@Override
    public void hide() {
        super.hide();
        lstItems.clear();
        updateSelection(lstItems.getSelected());
    }

	private void updateSelection(ItemEntry selected) {
		imgItem.setDrawable(controller.getManagedTexture(selected.getType().getImage()));
		txtDescription.setText(selected.getType().getDescription());
	}
}

libGDX Controllers

Now when I build a controller in libGDX, I give it several responsibilities:

Lord of the Lifecycle

First, it is responsible for orchestrating the game’s lifecycle. When the game is started, paused, resumed, and exited, it’s the controller’s job to tell the model and view what they need to do. Thus, in libGDX, my controller generally extends the Game class.

Guardian of the Model

It’s the interface to the model. Think of your controller as the common interface between your view and your model (at least when it comes to writes). It interprets input captured in the view into standard commands. If one player uses a touch screen, another uses a gamepad, and a third uses a mouse, their input is going to be captured differently, but ultimately my game logic is going to be the same and we will manipulate the model in the same way. The cool thing about this model is that once this interface is robust, it’s possible to start supporting use cases where you don’t have a view at all such as unit testing, AI4, or network peers.

Note: If your game is really simple, one reasonable corner to cut is having your view modify the model directly. The controller will still serve other purposes as we’ll see later.

libGDX already provides a fairly robust interface you can use to abstract your player’s input method with InputListener, but you can also have your controller take this a step further and use the Command Pattern to communicate with the Model.

The Signpost

The third responsibility that I assign to the controller is routing the player to different screens. There’s a certain amount of tear-down and set-up that needs to occur when the player transitions from one screen to another, and in order to keep the screens independent of each other (remember SoC!) I have the controller manage the lifecycle of each screen. As you saw when we discussed Views, each screen is still responsible for knowing what it needs to set up and tear down, but depends on the Controller to know when to act.

Note: We could take this a step further and use a Command Pattern to tell the Controller which screen comes next instead of passing the class name, but each time I’ve thought of doing it, it eventually turned out to add more complexity than I felt was justified. Thus I decided to stick to the KISS principle and not complicate things needlessly.

Abstractor of the OS

Finally, I use the Controller to abstract the Model from the underlying OS. The way files are manipulated can sometimes differ depending on whether we’re on a phone or a PC. The Game Logic and the Game Data don’t change, so I don’t want my model to change. This can be thought of as an extension of the Controller’s responsibility to govern interactions with the Model.

Example Controller

public class Controller extends Game {
	private ObjectMap<Class<? extends View>, View> screens = new ObjectMap<>();
	private boolean debugOn = false;
	private AssetManager assetManager;
	private Model world;

	// libGDX Game Methods //
	@Override
	public void create () {
    // Load the UI first
    VisUI.load();

		// Initialize the asset manager
		assetManager = new AssetManager();
		assetManager.load(ATLAS_NAME, TextureAtlas.class);
		assetManager.finishLoading();

    // Handle any debugging settings
    this.setDebugOn(false);

    world = new Model();

    // Load the screens
    loadScreens();
		this.changeScreen(MainMenuScreen.class);
	}

	@Override
	public void dispose () {
		// Dispose of the view
		setScreen(null);
		screens.forEach((e) -> e.value.dispose());
		screens.clear();
    if(VisUI.isLoaded()) VisUI.dispose();

		// Dispose of the model
		world.dispose();

		// Dispose of any other resources
		assetManager.dispose();
	}

	/**
	 * Convenience method to safely load textures. If the texture isn't found, a blank one is created and the error is logged.
	 * @param imageName The name of the image that is being looked up.
	 * @return
	 */
	public TextureRegionDrawable getManagedTexture(String imageName) {
		try {
			return new TextureRegionDrawable(assetManager.get(ATLAS_NAME, TextureAtlas.class).findRegion(imageName));
		} catch(Exception e) {
			Gdx.app.error(getClass().getCanonicalName(), "Couldn't get managed texture.", e);
			return getEmptyTexture();
		}
	}
	public TextureRegionDrawable getEmptyTexture() {
        return new TextureRegionDrawable(new TextureRegion(new Texture(new Pixmap(1,1, Pixmap.Format.RGBA8888))));
  }

	// Real Game Methods //
	/**
	 * Create a new game, starting with the story screen
	 */
	public void newGame() {
		world.startNewGame(this);
		this.changeScreen(IntroScreen.class);
	}

	// === Debug Logic === //
  public boolean isDebugOn() {
      return debugOn;
  }
	public Controller setDebugOn(boolean on) {
		this.debugOn = on;
		Gdx.app.setLogLevel(on ? Application.LOG_DEBUG : Application.LOG_INFO);
		return this;
	}

	// === Screen Management === //
	public void changeScreen(Class<? extends View> key) {
		this.setScreen(screens.get(key));
        handle(new GameEvent("SCREEN_CHANGE").set("SCREEN", screens.get(key)));
	}

	public void loadScreens() {
        screens.put(CargoScreen.class, new CargoScreen(this));
        screens.put(DepartureScreen.class, new DepartureScreen(this));
        screens.put(IntroScreen.class, new IntroScreen(this));
        screens.put(MainMenuScreen.class, new MainMenuScreen(this));
        screens.put(NewGameScreen.class, new NewGameScreen(this));
        screens.put(StationScreen.class, new StationScreen(this));
        screens.put(TradeScreen.class, new TradeScreen(this));
        screens.put(ClanScreen.class, new ClanScreen(this));
    }
}

Conclusion

Hopefully this description is helpful! If you have any questions, thoughts, or suggestions on how to improve this model, feel free to add a comment below! You can also subscribe at the bottom of the page if you’d like to be notified of future posts.

Save

  1. See Wikipedia for a good definition of MVC
  2. Also, if someone at your workplace dares utter the phrase “MVC compliance”, slap them for me
  3. In practice, it is extremely helpful to use libGDX utilities or data structures such as the Array or ObjectIntMap classes in your model classes, but these are relatively easy to refactor out of the code base
  4. Dr. Kimberly Voll has a really interesting GDC talk about how she used the controller interface to write her AI