dos opciones: si los casos de "entrada anidada" son como máximo tres, cuatro, simplemente usaría indicadores. "¿Sosteniendo un objeto? No se puede disparar". Cualquier otra cosa es sobregeniarlo.
De lo contrario, puede mantener una pila de controladores de eventos por clave de entrada.
Actions.Empty = () => { return; };
if(IsPressed(Keys.E)) {
keyEventHandlers[Keys.E].Push(Actions.Empty);
keyEventHandlers[Keys.LeftMouseButton].Push(Actions.Empty);
keyEventHandlers[Keys.Space].Push(Actions.Empty);
} else if (IsReleased(Keys.E)) {
keyEventHandlers[Keys.E].Pop();
keyEventHandlers[Keys.LeftMouseButton].Pop();
keyEventHandlers[Keys.Space].Pop();
}
while(GetNextKeyInBuffer(out key)) {
keyEventHandlers[key].Invoke(); // we invoke only last event handler
}
O algo por el estilo :)
Editar : alguien mencionó construcciones if-else inmanejables. ¿Vamos a manejar todos los datos para una rutina de manejo de eventos de entrada? Seguramente podrías, pero ¿por qué?
De todos modos, por el gusto de hacerlo:
void BuildOnKeyPressedEventHandlerTable() {
onKeyPressedHandlers[Key.E] = () => {
keyEventHandlers[Keys.E].Push(Actions.Empty);
keyEventHandlers[Keys.LeftMouseButton].Push(Actions.Empty);
keyEventHandlers[Keys.Space].Push(Actions.Empty);
};
}
void BuildOnKeyReleasedEventHandlerTable() {
onKeyReleasedHandlers[Key.E] = () => {
keyEventHandlers[Keys.E].Pop();
keyEventHandlers[Keys.LeftMouseButton].Pop();
keyEventHandlers[Keys.Space].Pop();
};
}
/* get released keys */
foreach(var releasedKey in releasedKeys)
onKeyReleasedHandlers[releasedKey].Invoke();
/* get pressed keys */
foreach(var pressedKey in pressedKeys)
onKeyPressedHandlers[pressedKey].Invoke();
keyEventHandlers[key].Invoke(); // we invoke only last event handler
Editar 2
Kylotan mencionó el mapeo de teclas, que es una característica básica que todo juego debería tener (piense también en la accesibilidad). Incluir mapas de teclas es una historia diferente.
Cambiar el comportamiento dependiendo de una combinación de teclas o secuencia es limitante. Pasé por alto esa parte.
El comportamiento está relacionado con la lógica del juego y no con la entrada. Lo cual es bastante obvio, ahora que lo pienso.
Por lo tanto, propongo la siguiente solución:
// //>
void Init() {
// from config file / UI
// -something events should be set automatically
// quake 1 ftw.
// name family key keystate
"+forward" "movement" Keys.UpArrow Pressed
"-forward" Keys.UpArrow Released
"+shoot" "action" Keys.LMB Pressed
"-shoot" Keys.LMB Released
"jump" "movement" Keys.Space Pressed
"+lstrafe" "movement" Keys.A Pressed
"-lstrafe" Keys.A Released
"cast" "action" Keys.RMB Pressed
"picknose" "action" Keys.X Pressed
"lockpick" "action" Keys.G Pressed
"+crouch" "movement" Keys.LShift Pressed
"-crouch" Keys.LShift Released
"chat" "user" Keys.T Pressed
}
void ProcessInput() {
var pk = GetPressedKeys();
var rk = GetReleasedKeys();
var actions = TranslateToActions(pk, rk);
PerformActions(actions);
}
void TranslateToActions(pk, rk) {
// use what I posted above to switch actions depending
// on which keys have been pressed
// it's all about pushing and popping the right action
// depending on the "context" (it becomes a contextual action then)
}
actionHandlers["movement"] = (action, actionFamily) => {
if(player.isCasting)
InterruptCast();
};
actionHandlers["cast"] = (action, actionFamily) => {
if(player.isSilenced) {
Message("Cannot do that when silenced.");
}
};
actionHandlers["picknose"] = (action, actionFamily) => {
if(!player.canPickNose) {
Message("Your avatar does not agree.");
}
};
actionHandlers["chat"] = (action, actionFamily) => {
if(player.isSilenced) {
Message("Cannot chat when silenced!");
}
};
actionHandlers["jump"] = (action, actionFamily) => {
if(player.canJump && !player.isJumping)
player.PerformJump();
if(player.isJumping) {
if(player.CanDoubleJump())
player.PerformDoubleJump();
}
player.canPickNose = false; // it's dangerous while jumping
};
void PerformActions(IList<ActionEntry> actions) {
foreach(var action in actions) {
// we pass both action name and family
// if we find no action handler, we look for an "action family" handler
// otherwise call an empty delegate
actionHandlers[action.Name, action.Family]();
}
}
// //<
Esto podría ser mejorado de muchas maneras por personas más inteligentes que yo, pero creo que también es un buen punto de partida.
Usamos un sistema de estado, como mencionaste antes.
Crearíamos un mapa que contendría todas las claves para un estado específico con una bandera que permitiría el paso de claves previamente asignadas o no. Cuando cambiamos de estado, se empujaría el nuevo mapa o se quitaría un mapa anterior.
Un ejemplo simple y rápido de estados de entrada sería Predeterminado, En menú y Modo mágico. El valor predeterminado es donde estás corriendo y jugando el juego. En el menú sería cuando esté en el menú de inicio, o cuando haya abierto un menú de la tienda, el menú de pausa, una pantalla de opciones. En el menú contendría la bandera de no pasar porque cuando navegas por un menú no quieres que tu personaje se mueva. Por otro lado, al igual que su ejemplo con el transporte del elemento, el modo mágico simplemente reasignaría las teclas de uso de acción / elemento para lanzar hechizos (también lo vincularíamos con los efectos de sonido y partículas, pero eso está un poco más allá tu pregunta).
Lo que hace que los mapas sean empujados y desplegados depende de usted, y también honestamente diré que tuvimos ciertos eventos 'claros' para asegurarnos de que la pila de mapas se mantuviera limpia, siendo la carga de nivel el momento más obvio (las escenas de corte también en veces).
Espero que esto ayude.
TL; DR: usa estados y un mapa de entrada que puedes empujar y / o reventar. Incluya una bandera para decir si el mapa elimina o no completamente la entrada anterior o no.
fuente
Esto parece un caso en el que la herencia podría resolver su problema. Podría tener una clase base con un montón de métodos que implementen el comportamiento predeterminado. Entonces podría extender esta clase y anular algunos métodos. El modo de cambio es solo una cuestión de cambiar la implementación actual.
Aquí hay un pseudocódigo
Esto es similar a lo que James propuso.
fuente
No estoy escribiendo el código exacto en ningún idioma en particular. Te estoy dando la idea.
1) Asigna tus acciones clave a tus eventos.
(Keys.LeftMouseButton, left_click_event), (Keys.E, e_key_event), (Keys.Space, space_key_event)
2) Asigna / modifica tus eventos como se indica a continuación
Deje que su acción de salto permanezca desacoplada con otros eventos como saltar y disparar.
Evite si ... más ... verificaciones condicionales aquí ya que conduciría a un código inmanejable.
fuente
secret cloak
esto requeriría que cosas como fuego, sprint, caminar y cambiar de arma no estén registradas y que no se registren.En lugar de cancelar el registro, simplemente haga un estado y luego vuelva a registrarse.
Por supuesto, ampliar esta simple idea sería que podría tener estados separados para moverse, y así, y luego, en lugar de dictar "Bueno, aquí están todas las cosas que no puedo hacer mientras estoy en el Modo de capa secreta, aquí están todas las cosas que puedo hacer en el modo de capa secreta ".
fuente