Zwei Optionen: Wenn "verschachtelte Eingaben" höchstens drei, vier sind, würde ich nur Flags verwenden. "Ein Objekt halten? Kann nicht schießen." Alles andere überarbeitet es.
Andernfalls können Sie einen Stapel von Ereignishandlern pro Eingabeschlüssel behalten.
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
}
Oder etwas in diesem Sinne :)
Bearbeiten : Jemand erwähnte nicht verwaltbare if-else-Konstrukte. Werden wir für eine Routine zur Behandlung von Eingabeereignissen vollständig datengesteuert sein? Sie könnten sicherlich, aber warum?
Wie auch immer, zum Teufel:
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
Bearbeiten 2
Kylotan erwähnte das Key Mapping, eine grundlegende Funktion, die jedes Spiel haben sollte (denken Sie auch an die Barrierefreiheit). Das Einschließen von Keymapping ist eine andere Geschichte.
Das Ändern des Verhaltens in Abhängigkeit von einer Tastendruckkombination oder -sequenz ist einschränkend. Ich habe diesen Teil übersehen.
Das Verhalten hängt mit der Spielelogik zusammen und nicht mit der Eingabe. Was ziemlich offensichtlich ist, wenn man daran denkt.
Daher schlage ich folgende Lösung vor:
// //>
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]();
}
}
// //<
Dies könnte in vielerlei Hinsicht von Leuten verbessert werden, die klüger sind als ich, aber ich glaube, es ist auch ein guter Ausgangspunkt.
Wir haben ein Staatssystem verwendet, wie Sie bereits erwähnt haben.
Wir würden eine Karte erstellen, die alle Schlüssel für einen bestimmten Status mit einem Flag enthält, das den Durchgang von zuvor zugeordneten Schlüsseln ermöglicht oder nicht. Wenn wir den Status ändern, wird die neue Karte weitergeschoben oder eine vorherige Karte wird entfernt.
Ein einfaches Beispiel für Eingabestatus wäre Standard, In-Menu und Magic-Mode. Standardmäßig laufen Sie herum und spielen das Spiel. In-Menu ist, wenn Sie sich im Startmenü befinden oder wenn Sie ein Shop-Menü, das Pausenmenü und einen Optionsbildschirm geöffnet haben. In-Menu würde das Flag no pass through enthalten, da Sie beim Navigieren in einem Menü nicht möchten, dass sich Ihr Charakter bewegt. Auf der anderen Seite würde der Magic-Modus, ähnlich wie in Ihrem Beispiel beim Tragen des Gegenstands, einfach die Aktions- / Gegenstands-Verwendungstasten neu zuordnen, um stattdessen Zauber zu wirken (wir würden das auch mit Klang- und Partikeleffekten verknüpfen, aber das ist etwas weiter Ihre Frage).
Was dazu führt, dass die Karten verschoben und geknallt werden, liegt bei Ihnen, und ich werde auch ehrlich sagen, dass wir bestimmte "klare" Ereignisse hatten, um sicherzustellen, dass der Kartenstapel sauber gehalten wurde, wobei das Laden auf gleicher Ebene die naheliegendste Zeit war (auch Zwischensequenzen) mal).
Hoffe das hilft.
TL; DR - Verwenden Sie Status und eine Eingabekarte, die Sie drücken und / oder einfügen können. Fügen Sie ein Flag hinzu, um anzugeben, ob die Karte vorherige Eingaben vollständig entfernt oder nicht.
quelle
Dies scheint ein Fall zu sein, in dem die Vererbung Ihr Problem lösen könnte. Sie könnten eine Basisklasse mit einer Reihe von Methoden haben, die das Standardverhalten implementieren. Sie können diese Klasse dann erweitern und einige Methoden überschreiben. Das Umschalten des Modus ist dann nur eine Frage des Umschaltens der aktuellen Implementierung.
Hier ist ein Pseudocode
Dies ähnelt dem, was James vorgeschlagen hat.
quelle
Ich schreibe nicht den genauen Code in einer bestimmten Sprache. Ich gebe dir die Idee.
1) Ordnen Sie Ihre Schlüsselaktionen Ihren Ereignissen zu.
(Keys.LeftMouseButton, left_click_event), (Keys.E, e_key_event), (Keys.Space, space_key_event)
2) Weisen Sie Ihre Ereignisse wie unten angegeben zu / ändern Sie sie
Lassen Sie Ihre Sprungaktion mit anderen Ereignissen wie Sprung und Feuer entkoppelt bleiben.
Vermeiden Sie, wenn ... sonst ... bedingte Überprüfungen durchgeführt werden, da dies zu nicht verwaltbarem Code führen würde.
quelle
secret cloak
würde jedoch erfordern, dass Dinge wie Feuer, Sprint, Gehen und Waffenwechsel nicht registriert und der Umhang nicht registriert werden.Anstatt die Registrierung aufzuheben, erstellen Sie einfach einen Status und registrieren Sie sich erneut.
Eine Erweiterung dieser einfachen Idee wäre natürlich, dass Sie möglicherweise separate Zustände für das Bewegen und dergleichen haben und dann, anstatt zu diktieren: "Nun, hier sind alle Dinge, die ich im geheimen Umhangmodus nicht tun kann , hier sind alle Dinge, die ich kann." im geheimen Umhangmodus. "
quelle