duas opções: se os casos de "entrada aninhada" tiverem no máximo três, quatro, eu apenas usaria sinalizadores. "Segurando um objeto? Não é possível disparar." Qualquer outra coisa está sobrecarregando.
Caso contrário, você pode manter uma pilha de manipuladores de eventos por chave 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
}
Ou algo nesse sentido :)
Edit : alguém mencionou construções if-else incontroláveis. vamos orientar todos os dados para uma rotina de manipulação de eventos de entrada? Você certamente poderia, mas por quê?
De qualquer forma, para o inferno:
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 mencionou o mapeamento de teclas, que é um recurso básico que todo jogo deve ter (pense também em acessibilidade). Incluir mapeamento de teclas é uma história diferente.
Alterar o comportamento dependendo de uma combinação ou sequência de pressionamento de tecla é limitador. Eu ignorei essa parte.
O comportamento está relacionado à lógica do jogo e não à entrada. O que é bastante óbvio, pensando nisso.
Portanto, estou propondo a seguinte solução:
// //>
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]();
}
}
// //<
Isso pode ser melhorado de várias maneiras por pessoas mais inteligentes que eu, mas acredito que também é um bom ponto de partida.
Usamos um sistema de estados, como você mencionou antes.
Criaríamos um mapa que conteria todas as chaves para um estado específico com um sinalizador que permitiria a passagem de chaves mapeadas anteriormente ou não. Quando mudamos de estado, o novo mapa seria acionado ou um mapa anterior seria acionado.
Um exemplo simples e rápido de estados de entrada seria Padrão, No menu e Modo Mágico. O padrão é onde você está correndo e jogando o jogo. No menu seria quando você está no menu Iniciar ou quando abre um menu de loja, o menu de pausa, uma tela de opções. O menu no menu conteria o sinalizador de não passagem, pois ao navegar no menu, você não quer que seu personagem se mova. Por outro lado, muito parecido com o seu exemplo com o porte do item, o Modo Mágico simplesmente remapearia as teclas de ação / item para lançar feitiços (também amarrávamos isso aos efeitos sonoros e de partículas, mas isso é um pouco além sua pergunta).
O que faz com que os mapas sejam empurrados e estourados é com você, e também honestamente digo que tivemos alguns eventos 'claros' para garantir que a pilha de mapas fosse mantida limpa, sendo o carregamento nivelado o momento mais óbvio (Cutscenes, bem como vezes).
Espero que isto ajude.
TL; DR - Use estados e um mapa de entrada que você pode pressionar e / ou pop. Inclua um sinalizador para dizer se o mapa remove ou não completamente a entrada anterior.
fonte
Parece um caso em que a herança pode resolver seu problema. Você pode ter uma classe base com vários métodos que implementam o comportamento padrão. Você pode estender essa classe e substituir alguns métodos. O modo de comutação é apenas uma questão de alternar a implementação atual.
Aqui estão alguns pseudo-códigos
Isso é semelhante ao que James propôs.
fonte
Não estou escrevendo o código exato em nenhum idioma específico. Estou te dando uma idéia.
1) Mapeie suas ações principais para seus eventos.
(Keys.LeftMouseButton, left_click_event), (Keys.E, e_key_event), (Keys.Space, space_key_event)
2) Atribua / modifique seus eventos como indicado abaixo
Deixe sua ação de salto permanecer dissociada com outros eventos como salto e tiro.
Evite as verificações condicionais if..else .. aqui, pois isso levaria a um código incontrolável.
fonte
secret cloak
isso exigiria que coisas como fogo, sprint, caminhada e troca de armas não fossem registradas e que a capa fosse registrada.Em vez de cancelar o registro, apenas faça um estado e depois registre-se novamente.
É claro, estender essa idéia simples seria que você pode ter estados separados para se mudar, e tal, e depois em vez de ditar "Bem, aqui estão todas as coisas que não posso fazer enquanto estiver no modo Secret Cloak, aqui estão todas as coisas que posso faça no modo Secret Cloak. ".
fonte