dwie opcje: jeśli przypadki „wejścia zagnieżdżonego” wynoszą co najwyżej trzy, cztery, po prostu użyłbym flag. „Trzymanie przedmiotu? Nie można strzelać”. Wszystko inne go przerabia.
W przeciwnym razie można przechowywać stos obsługi zdarzeń dla klucza wejściowego.
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
}
Lub coś w tym celu :)
Edycja : ktoś wspomniał o niemożliwych do zarządzania konstrukcjach if-else. czy zamierzamy przejść w pełni sterowane danymi na procedurę obsługi zdarzeń wejściowych? Z pewnością mógłbyś, ale dlaczego?
W każdym razie, do cholery:
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
Edytuj 2
Kylotan wspomniał o mapowaniu klawiszy, które jest podstawową funkcją każdej gry (pomyśl także o dostępności). Włączanie mapowania klawiszy to inna historia.
Zmiana zachowania w zależności od kombinacji klawiszy lub sekwencji jest ograniczona. Przeoczyłem tę część.
Zachowanie jest związane z logiką gry, a nie wejściem. Co jest dość oczywiste, gdy o tym pomyślę.
Dlatego proponuję następujące rozwiązanie:
// //>
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]();
}
}
// //<
Ludzie mądrzejsi ode mnie mogą to poprawić na wiele sposobów, ale uważam, że to także dobry punkt wyjścia.
Użyliśmy systemu państwowego, jak wspomniałeś wcześniej.
Stworzylibyśmy mapę, która zawierałaby wszystkie klucze dla określonego stanu z flagą, która pozwalałaby na przejście wcześniej mapowanych kluczy lub nie. Kiedy zmieniliśmy stany, nowa mapa byłaby popychana lub poprzednia mapa byłaby usuwana.
Szybki prosty przykład stanów wejściowych to Domyślny, W menu i Tryb magiczny. Domyślnie biegasz i grasz. In-Menu będzie wtedy, gdy będziesz w menu Start lub gdy otworzysz menu sklepu, menu pauzy, ekran opcji. In-Menu zawiera flagę zakazu przejścia, ponieważ podczas poruszania się po menu nie chcesz, aby twoja postać się poruszała. Z drugiej strony, podobnie jak twój przykład z noszeniem przedmiotu, Tryb Magii po prostu zamapowałby klawisze akcji / przedmiotu, aby zamiast tego rzucić zaklęcia (powiązalibyśmy to również z efektami dźwiękowymi i cząsteczkowymi, ale to nieco więcej Twoje pytanie).
To, co powoduje, że mapy są wypychane i wyskakujące, zależy od Ciebie, a także szczerze powiem, że mieliśmy pewne „jasne” zdarzenia, aby upewnić się, że stos map został utrzymany w czystości, a ładowanie poziomów jest najbardziej oczywistym czasem (przerywniki również w czasy).
Mam nadzieję że to pomoże.
TL; DR - Używaj stanów i mapy wejściowej, którą możesz popychać i / lub pop. Dołącz flagę informującą, czy mapa całkowicie usuwa poprzednie dane wejściowe, czy nie.
źródło
Wygląda to na przypadek, w którym dziedziczenie mogłoby rozwiązać twój problem. Możesz mieć klasę podstawową z wieloma metodami, które implementują domyślne zachowanie. Następnie możesz rozszerzyć tę klasę i zastąpić niektóre metody. Tryb przełączania jest wtedy tylko kwestią przełączenia bieżącej implementacji.
Oto pseudo-kod
Jest to podobne do tego, co zaproponował James.
źródło
Nie piszę dokładnego kodu w żadnym konkretnym języku. Daję ci pomysł.
1) Zamapuj swoje kluczowe działania na wydarzenia.
(Keys.LeftMouseButton, left_click_event), (Keys.E, e_key_event), (Keys.Space, space_key_event)
2) Przypisz / zmień swoje wydarzenia, jak podano poniżej
Niech twoja akcja skoku pozostanie oddzielona od innych wydarzeń, takich jak skok i strzelanie.
Unikaj sprawdzania warunkowego if..else .., ponieważ prowadziłoby to do niemożliwego do zarządzania kodu.
źródło
secret cloak
wymagałoby rzeczy, takich jak pożar, sprint, chodzić, i zmienić broń zostanie wyrejestrowana i demaskować być zarejestrowany.Zamiast wyrejestrować się, po prostu wprowadź stan, a następnie ponownie się zarejestruj.
Oczywiście, rozszerzenie tego prostego pomysłu byłoby takie, że możesz mieć oddzielne stany ruchu, i takie, a następnie zamiast dyktować „Cóż, oto wszystkie rzeczy, których nie mogę zrobić w trybie Sekretnego Płaszcza, oto wszystkie rzeczy, które mogę zrobić w trybie Secret Cloak. ”.
źródło