Sto progettando un sistema di gestione "vista" per un gioco. L'obiettivo è essere in grado di avere diverse "viste" che possono essere mostrate in sequenza o impilate l'una sull'altra. Ad esempio, la schermata iniziale iniziale è una View
e il menu principale che segue la schermata iniziale è anche un View
che viene visualizzato al termine della schermata iniziale. Anche la visualizzazione della telecamera del mondo di gioco è un View
e l'HUD è un View
che viene visualizzato in cima al gioco View
.
Durante la definizione dell'interfaccia per View
mi rendo conto che la natura impilata di View
s corrisponde alla priorità del fuoco di input. Cioè il View
che è in cima allo stack dovrebbe essere offerto per agire su qualsiasi input dell'utente prima di qualsiasi altro View
s perché in sostanza è in "foreground" per lo stato attivo.
Quindi il design dell'interfaccia per View
sembra al momento:
/**
* Interface for a "view". A view is a renderable target that can accept user input.
*
* One can think of it as a "layer", many layers can be drawn over each other in a
* stack like fashion where the input focus travels from the top to the bottom of
* the stack.
*/
class IView{
public:
virtual ~IView() {}
IView(const IView&) = delete; // Not allowed
void operator = (const IView&) = delete; // Not allowed
/**
* Called to handle user keyboard input. May be called multiple times per update.
* @param aKeyEvent The type of input event.
* @param aKeyCode The key code of the event key.
* @param aKeyboardState The current state of they keyboard.
* @return True if the event was handled. False if the event should continue to lower views.
*/
virtual bool handleInput(KeyEvent aKeyEvent, KeyCode aKeyCode, const KeyboardState& aKeyboardState) = 0;
/**
* Called to handle user mouse clicks. May be called multiple times per update.
* @param aMouseButtonEvent The event to handle.
* @param aMouseButton Which button it was.
* @param aMouseState The current mouse state.
* @return True if the event was handled. False if the event should continue to lower views.
*/
virtual bool handleInput(MouseButtonEvent aMouseButtonEvent, MouseButton aMouseButton,
const MouseState& aMouseState) = 0;
/**
* Called to handle user mouse movement. Only called once per update.
* @param aMouseEvent The event to handle.
* @param aMouseState
* @return True if the event was handled. False if the event should continue to lower views.
*/
virtual bool handleInput(MouseMoveEvent aMouseMoveEvent, const MouseState& aMouseState) = 0;
/**
* Called when this view becomes the foreground view.
*/
virtual void foreground() = 0;
/**
* Called when this view loses its foreground status and
* has another view drawn over it.
*/
virtual void background() = 0;
/**
* Called once every frame to perform periodic processing and rendering.
*/
virtual void update() = 0;
};
Questa non è l'interfaccia finale, sto solo prototipando al momento. Ma ti dà un'idea.
Considera un gioco View
, con un HUD in cima, e quindi il gioco viene messo in pausa in modo che una pausa View
sia resa in cima all'HUD. La pausa View
offre la prima possibilità di agire sull'input dell'utente per continuare il gioco e ingoia tutti gli altri input. Per me questo ha perfettamente senso.
Ora, non tutti i View
s sono necessariamente interessati a ricevere input. Alcuni potrebbero essere interessati solo agli eventi del mouse e alcuni potrebbero essere interessati solo agli eventi della tastiera. Il mio approccio iniziale era di lasciargli avere gestori di input pass-through. Ma poi ho ricordato il principio di segregazione dell'interfaccia , quindi avrebbe senso definire altre due interfacce IKeyboardHandler
e IMouseHandler
e lascia che le pertinenti classi View
implementino quelle quando necessario. Ma poi ho bisogno di usare dynamic_cast
quando passi l'input agli oggetti View
per vedere se un particolare View
implementa per esempio IKeyboardHandler
.
Non sono sicuro che questo approccio sia più elegante di avere gestori di input vuoti e sto cercando input e idee sul design.