How do I handle "screens"?

Alright, so I’m working “hard” on the gui for my game. But I’m wondering how best to handle different screens.

Currently I have an interface called Screen, which all screens implement, this interface defines an update() method and a render() method. So these has to be implemented whenever you create a new screen. My question is, how best to handle new screens, e.g. when you’re in the main menu and you go to a submenu, etc. how should I go about switching them?

Should I just replace the content of the screen variable, in the Game class, with a new instance of a screen, and then just reinitialize the screen whenever I go back to a previous menu?

Cheers! :slight_smile:

I’ve got the notion of a stack of Screens (held statically in my Screen class, meh, you might want to do something like a ScreenManager but whatever…). Only the screen on top of the stack accepts input from the mouse and keyboard. Every time I open a screen it can do one of two things:

  1. If it’s a totally new screen, the current screen on top of the stack is popped and discarded and the new screen is plonked on top.
  2. If it’s a “dialog” screen it gets plonked on top of the stack.

All the screens in the stack are updated and rendered, one after the other, every frame. However only the topmost one actually processes input. In each screen’s update method, for example, there’s a little check:


if (!isOnTop()) { return; }

at some point; what I do is allow certain things to still tick away like animations and so on in the background, but stop short of then processing input at that point.

Cas :slight_smile:

Hrm, I never thought of doing it like that, that even solves the issue of dialogs and such.

Thanks a whole bunch! :smiley:

I use the screen stack idea too, although I’m thinking about flagging screens I want to stop updating, maybe if they are covered by a new screen that takes up the whole display.

And maybe using the stack as a Z-depth check to allow switching from foreground to background screens (menus, sub-windows) via mouse input, although that would mess up the stack and need a linked list or something.

A question though. Regarding the Screen class in Java. I want to use the most barebones container for the graphics context, as I do all the drawing myself and have no intention of using Java2d items. Is Screen the best option? Window? Canvas? I get a bit confused with the inheritance between them.

for my Screens I wrote something of a GUI Manager which is dedicated to managing and layering of GUI elements. For example, the user can create a GUIGroup which can store things like buttons, labels and graphic areas (fancy on-screen stuff). If the GUIGroup is “hidden” no rendering or input is performed on it or any of the elements that belong to it. This allows great control of say showing and hiding a number of groups at same time. It is considered a complete separate entity from the screen itself, it allows for greater control and abstraction from GUI elements to in-game elements such as GameObjects.

This:


public class ScreenManager {
	private static final LinkedList<Screen> screens = new LinkedList<Screen>();
	
	public static void add(Screen screen) {
		ScreenManager.screens.push(screen);
	}

	public static void render(Graphics2D g) {
		for(Screen screen : ScreenManager.screens) {
			screen.render(g);
		}
	}

	public static void update() {
		for(Iterator<Screen> it = ScreenManager.screens.iterator(); it.hasNext(); ) {
			Screen screen = it.next();
			screen.update();
			
			if(screen.shouldRemove()) {
				it.remove();
			}
		}
	}
}

is what I’ve cooked up so far, but I haven’t tested it at all yet. (And frankly, then I don’t think that will really work as well as I want)

But could something like that work? I know it’s still missing some stuff, for it to be perfectly usable.

I’m drawing on a Canvas. :slight_smile:

(I know it doesn’t matter in this case, but use ArrayList instead of LinkedList. Pardon the interruption.)

State machine and decks. (Or so I heard. I’m not sure and have never made anything with more than half a dozen screens. Someone fill in the rest.)

Interruption pardonen :stuck_out_tongue: But isn’t LinkedList faster than ArrayList, when you only push, pop and iterate?


Alright, I’ve now had a little time to actually try and make something that seems to be working as I’d like. The class now looks like this:

package org.infloop.main;


import java.awt.Dimension;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.LinkedList;

import org.infloop.ui.Screen;

public class ScreenManager {
	private static final ArrayList<Screen> screens = new ArrayList<Screen>();	
	private static final ArrayList<Screen> screensToAdd = new ArrayList<Screen>();
	private static int screensToAddCount = 0;
	
	public static Dimension screenDimension = new Dimension();
	
	public static void add(Screen screen) {
		ScreenManager.screensToAdd.add(screen);
		ScreenManager.screensToAddCount++;
	}
	
	/**
	 * Determine if the {@code Screen} is the top one in the stack.
	 *
	 * @param screen the screen to check
	 * @return true if the {@code Screen} is on top
	 */
	public static boolean isOnTop(Screen screen) {
		return ScreenManager.screens.get(ScreenManager.screens.size() - 1) == screen;
	}
	
	/**
	 * Render all the {@code Screen}'s.
	 * 
	 * @param g the graphics object to render the screens on
	 */
	public static void render(Graphics2D g) {
		for(int i = ScreenManager.screens.size()-1; i >= 0; i--) {
			ScreenManager.screens.get(i).render(g);
		}
	}
	
	/**
	 * Update all the {@code Screen}'s.
	 */
	public static void update() {
		LinkedList<Screen> toBeRemoved = new LinkedList<Screen>();
		
		// Update all Screens
		for(Screen screen : ScreenManager.screens) {
			screen.update();
			
			if(screen.shouldRemove()) {
				toBeRemoved.push(screen);
			}
		}
		
		// Remove Screens
		for(Screen screen : toBeRemoved) {
			ScreenManager.screens.remove(screen);
		}
		
		// Add new Screens
		if(ScreenManager.screensToAddCount > 0) {
			for(Screen screen : ScreenManager.screensToAdd) {
				ScreenManager.screens.add(screen);
			}
			ScreenManager.screensToAdd.clear();
			ScreenManager.screensToAddCount = 0;
		}
	}
}

So now just to figure out how to allow animations in components to continue running, without the component answering to input, unless the screen is on top. I think I might just either split update() into updateAnimation() and updateInput() or change it to update(boolean allowInput).

I say

  • isOnTop shouldn’t break if there are no screens at all
  • screensToAddCount can be replaced by screensToAdd.size()
  • toBeRemoved could be a field as well
  • no good reason to make this all static

Ups, fixed the isOnTop, so it returns false if there’s no screens, instead of throwing an index out of bounds exception. Thanks for noticing. :slight_smile:
I also removed the count and just use size() instead. No idea why I didn’t just do that to begin with. :confused:

The reason it’s static, is so that I don’t have to pass references around to the screens. :slight_smile: Though I guess the only real overhead for not making them static would be that I’d have to type more to pass around the reference. :stuck_out_tongue:

It would be better to have the dependency only point in one direction: from ScreenManager to Screen.
Screens should not know about the underlying overall screen handling.
Besides removing the static stuff, maybe split the ScreenManager, give screens a callback interface, register listeners, something like that.

Hm, well a screen should be able to add new screens to the stack, so it needs to be able to talk to the ScreenManager ^^ (at least through the add() function)

How’d you go about making it non-static without having to pass references around? The screen would need to know something of the manager, or else it can’t add new screens.

only use static methods for utility functions.
Remove all the statics, then you can easily create a single instance of the object, which reference is somehow passed around.
This so called Singelton can be made a static final instance, like many do on this board.
Or what I prefer, hand the reference always around so you don’t have any hidden dependecies.


public class ScreenManager {
private static final ScreenManager instance = new ScreenManager();
public static ScreenManager getInstance(){return instance;}

Passing and accessing references is daily business in object orientation and it is inevitable to get accustomed to it. Yes, it is sometimes annoying. But it’s good to first know how to get by with it before breaking this rule for appropriate exceptions.

For restricting dependencies, you could have an interface and give each screen a reference to it and let the manager implement it.

public interface ScreenController {
   void addScreen(Screen screen);
   void removeScreen(Screen screen);
   ...
}

So whenever a screen feels like adding another one, it just addresses the controller, says “hey guy, please add this new screen for me. By the way, I don’t care who you are or how you do it. Just do it.”
Only add methods that screens need to call.

What if one day you want a new screen manager which draws some fancy additional debug graphics ? Just let it implement the interface, create the object and hand it over to the screens. No change in any screen required. Could even be switched during runtime.

One more thought:
What if you implement a whole load of debug screens which are useful to watch in parallel to the game ? Then you will most likely have two screen managers running in parallel, each in its own native window context. Only possible without a static setup.
The state of the ScreenManager (lists of screens) is an indicator that statics are not that good here and a possible dead end.
Utility functions on the other hand are typically context free, they get all parameters with one method call. No side effects when called in parallel.

And thusly another 50 classes were written that did the same job :slight_smile: Sigh.

Cas :slight_smile:

Sometimes you need to reinvent the wheel so it fits your specific needs. ;D

I tend to reinvent the wheel all the time, because I refuse to use solutions other people have cooked up, until I know how to do it myself :stuck_out_tongue:

However I’m fairly happy with how my Screen managing system has worked out. There’s a few kinks left in it, that I’ll have to solve eventually, but right now there’s too much school stuff going on, for me to have proper time to look at it. Dat uni, so strong! xD

Well, if you’re learning, it is actually advisable to reinvent as many wheels as necessary to perfect your skills :wink: (That’s what I do, in any case)