I’m making a tower defense game using libgdx and scene2d. For my UI during gameplay, I implemented a State Pattern (or at least a variant) along with MVP and Observer pattern.
To do this, I have a view, presenter, and uiStateManager class. The uiStateManager is referenced by various presenters and services to alter that state of the UI. For instance, when placing a tower on the map, the HUD should disable buttons, hide certain widgets, etc.
The following is my uiStateManager class:
public class GameUIStateManager {
private GameUIState state;
private CopyOnWriteArrayList<IGameUIStateObserver> observers = new CopyOnWriteArrayList<IGameUIStateObserver>();
public GameUIStateManager(){
this.setState(GameUIState.STANDBY); //init in standby
}
public void attach(IGameUIStateObserver observer){
observers.add(observer);
}
public void notifyObservers(){
for(IGameUIStateObserver observer : observers){
observer.changeUIState(state);
}
}
public void setState(GameUIState state){
this.state = state;
notifyObservers();
}
public GameUIState getState(){
return state;
}
public enum GameUIState {
INSPECTING,
OPTIONS,
STANDBY,
GAME_OVER,
PLACING_TOWER;
}
}
So the UIStateManager holds a list of IGameUIStateObservers which looks like:
public interface IGameUIStateObserver {
public void changeUIState(GameUIState state);
}
The State Manager notifies all of its observers whenever it changes states.
So for instance, my HUD Presenter implements IGameUIStateObserver and attaches itself to the uiGameStateManager object.
When it is notified that the uiStateManager has changed states, it uses a switch to determine how the HUD View should update itself.
Method inside HUD Presenter:
@Override
public void changeUIState(GameUIState state) {
switch(state){
case GAME_OVER:
view.gameOverState();
break;
case OPTIONS:
view.optionsState();
break;
case STANDBY:
view.standByState();
break;
default:
break;
}
}
So for example when the StateManager changes to GAME_OVER, it notifies my HUD Presenter which calls the gameOverState method on the HUD View. The HUD view then updates itself to disable buttons.
Method in HUD View:
public void gameOverState(){
btnEnlist.setTouchable(Touchable.disabled);
btnWave.setTouchable(Touchable.disabled);
btnSpeedGroup.setTouchable(Touchable.disabled);
btnOptions.setTouchable(Touchable.disabled);
}
The GameOver Presenter is also notified to render itself and show the GameOver overlay which shows various buttons and other widgets. However, the game is still in the background with the disabled buttons.
Method in GameOver Presenter:
public void changeUIState(GameUIState state) {
switch(state){
case GAME_OVER:
view.gameOverState();
setWavesCompleted();
break;
default:
view.standByState();
break;
}
}
public void setWavesCompleted() {
view.setWavesCompleted(String.valueOf(player.getWavesCompleted()));
}
Method in GameOverView:
@Override
public void gameOverState(){
this.setVisible(true);
}
@Override
public void setWavesCompleted(String wavesCompleted){
lblWavesCompletedCount.setText(wavesCompleted);
}
So the GameOverView also updates itself, although it isn’t as complex as the HUD.
I know switch statements are a code smell, and I’ve been trying to come up with a way to improve on this design. However, nothing seems to be as flexible and easy as this. I tried to come up with a polymorphic way to handle this with actual concrete state classes, but the issue is that the views update themselves. I could do away with the whole MVP pattern and just have State classes and Views. Then pass in as a parameter the various views to the State Class. So for example, a state class called GameOver would have references to the HUD View and GameOverView. It would then call hudView.gameOverState() and gameOverView.gameOverState() on the views themselves, but I’m not sure if that helps at all.
How can I refactor my design pattern? I like what I have now, but I would like to learn other alternatives and if possible, improvements.
Thanks!