Вкладений вхід у систему, керовану подіями


11

Я використовую систему обробки входів на основі подій із подіями та делегатами. Приклад:

InputHander.AddEvent(Keys.LeftArrow, player.MoveLeft); //Very simplified code

Однак я почав цікавитися, як боротися з «вкладеним» введенням. Наприклад, у програмі Half-Life 2 (чи справді будь-яка гра Source) ви можете забрати предмети E. Коли ви забрали предмет, ви не можете стріляти з нього Left Mouse, а натомість ви кинете предмет. Ще можна стрибати Space.

(Я кажу, що вкладений ввід - це те, коли ви натискаєте певну клавішу, і дії, які ви можете зробити, змінюються. Не меню.)

Три випадки:

  • Вміти робити ті ж дії, що і раніше (як стрибки)
  • Не в змозі зробити ті ж дії (як стрілянина)
  • Виконуючи різні дії цілком (наприклад, у NetHack, коли натискання клавіші відкритої двері означає, що ви не рухаєтесь, а вибираєте напрямок, щоб відкрити двері)

Моя первісна ідея полягала в тому, щоб просто змінити її після отримання даних:

Register input 'A' to function 'Activate Secret Cloak Mode'

In 'Secret Cloak Mode' function:
Unregister input 'Fire'
Unregister input 'Sprint'
...
Register input 'Uncloak'
...

Це страждає від великої кількості з'єднання, повторюваного коду та інших поганих ознак дизайну.

Я думаю, іншим варіантом є підтримка якоїсь системи стану введення - можливо, інший делегат функції реєстрації, щоб перефактурувати ці численні регістри / зняття з реєстрації в більш чисте місце (з певним стеком у системі введення) або, можливо, масиви чого тримати, а чого не робити.

Я впевнений, що хтось тут, напевно, стикався з цією проблемою. Як ти це вирішив?

tl; dr Як я можу мати справу з конкретним входом, отриманим після іншого конкретного введення в системі подій?

Відповіді:


7

два варіанти: якщо випадків "вкладеного вводу" максимум три, чотири, я б просто застосував прапори. "Утримує предмет? Не можна стріляти." Все інше - перебільшує це.

В іншому випадку ви можете зберегти стопку вхідних ключів на обробку подій.

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
}

Або щось до цього ефекту :)

Редагувати : хтось згадав некеровані конструкції if-else. ми збираємось використовувати повний облік даними для рутинної обробки подій? Ви, звичайно, могли, але чому?

У всякому разі, для чорта цього:

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

Редагувати 2

Kylotan згадав ключове відображення, яке є основною особливістю кожної гри (також подумайте про доступність). Включно із створенням клавіш - це інша історія.

Зміна поведінки залежно від комбінації натискання клавіш або послідовності обмежує. Я пропустив цю частину.

Поведінка пов'язана з логікою гри, а не входом. Що досить очевидно, придумуючи це.

Тому я пропоную таке рішення:

// //>

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]();    
    }
}

// //<

Це можна багато в чому покращити людьми, які розумніші за мене, але я вважаю, що це також є гарною відправною точкою.


Добре працює для простих ігор - тепер, як ви збираєтеся кодувати для цього ключ-картограф? :) Це одне може бути достатньо вагомим приводом для того, щоб передати дані для вашого введення.
Kylotan

@Kylotan, це добре спостереження, я хочу відредагувати свою відповідь.
Дощ

Це чудова відповідь. Ось, майте виграш: П
Качка комуніста

@Комуністична качка - спасибі хлопець, сподіваюся, це допоможе.
Дощ

11

Ми використовували державну систему, як ви вже згадували раніше.

Ми створили б карту, яка містила б усі ключі для певного стану з прапором, який би дозволяв пропускати раніше відображені ключі чи ні. Коли ми змінили штати, нова карта буде натиснута або попередня карта вискочить.

Швидкий простий приклад станів введення - Стандартні, В меню та Магічний режим. За замовчуванням - там, де ви бігаєте і граєте в гру. У меню буде, коли ви знаходитесь у меню "Пуск" або коли ви відкрили магазинне меню, паузу, екран параметрів. В меню міститься прапор без пропуску, оскільки під час переміщення по меню ви не хочете, щоб ваш персонаж переміщався. З іншого боку, як і ваш приклад із перенесенням предмета, Magic-Mode просто перезаписує клавіші дії / елемента, використовуючи клавіші замість того, щоб робити заклинання (ми також прив'язуємо це до звукових та частинкових ефектів, але це трохи вище твоє запитання).

Те, що спричиняє виштовхування та виштовхування карт, залежить від вас, і я також чесно скажу, що у нас були певні «чіткі» події, щоб переконатися, що стек карт залишається чистим, а завантаження рівня є найочевиднішим часом (як і куццини в разів).

Сподіваюсь, це допомагає.

TL; DR - Використовуйте стани та карту введення, яку можна натискати та / або пересувати. Включіть прапор, щоб сказати, чи карта повністю видаляє попередній вхід чи ні.


5
ЦЕЙ. Сторінки та сторінки вкладені, якщо твердження є дияволом.
michael.bartnett

+1. Коли я думаю про вхід, я завжди маю на увазі програму Acrobat Reader - виберіть інструмент, ручний інструмент, масштабування масштабу. IMO, використання стека може бути надмірно часом. GEF приховує це через AbstractTool . JHotDraw має прекрасний ієрархічний вигляд їхніх інструментів реалізації .
Стефан Ханке

2

Це схоже на випадок, коли спадкування може вирішити вашу проблему. У вас може бути базовий клас із купою методів, які реалізують поведінку за замовчуванням. Потім ви можете розширити цей клас і змінити деякі методи. Режим перемикання - це лише питання переключення поточної реалізації.

Ось псевдокод

class DefaultMode
    function handle(key) {/* call the right method based on the given key. */}
    function run() { ... }
    function pickup() { ... }
    function fire() { ... }


class CarryingMode extends DefaultMode
      function pickup() {} //empty method, so no pickup action in this mode
      function fire() { /*throw object and switch to DefaultMode. */ }

Це схоже на те, що запропонував Джеймс.


0

Я не пишу точний код якоюсь конкретною мовою. Я даю вам ідею.

1) Наміть ключові дії на свої події.

(Keys.LeftMouseButton, left_click_event), (Keys.E, e_key_event), (Keys.Space, space_key_event)

2) Призначайте / змінюйте свої події, як зазначено нижче

def left_click_event = fire();
def e_key_event = pick_item();
def space_key_event = jump();

pick_item() {
 .....
 left_click_action = throw_object();
}

throw_object() {
 ....
 left_click_action = fire();
}

fire() {
 ....
}

jump() {
 ....
}

Нехай ваша стрибкова дія залишатиметься роз'єднаною з іншими подіями, такими як стрибок та вогонь.

Уникайте if..else .. умовних перевірок тут, оскільки це призведе до некерованого коду.


Це, здається, мені зовсім не допомагає. Він має проблему високого рівня зчеплення і, схоже, має повторюваний код.
Качка комуністична

Просто для кращого розуміння - Чи можете ви пояснити, що в цьому "повторюється" і де ви бачите "високий рівень зчеплення".
inRazor

Якщо ви бачите приклад мого коду, я повинен видалити всі дії в самій функції. Я важко кодую все це до самої функції - що робити, якщо дві функції хочуть ділитися одним і тим же реєстром / нерегістраторами? Мені доведеться або дублювати код, або доведеться їх спарювати. Також буде велика кількість коду для видалення всіх небажаних дій. Нарешті, я мав би мати якесь місце, яке «запам’ятовує» всі початкові дії для їх заміни.
Качка комуністична

Чи можете ви надати мені дві фактичні функції з вашого коду (як-от секретна функція маскування) з повним виписком реєстру / скасування реєстрації.
inRazor

Наразі я не маю коду для цих дій. Однак, secret cloakпотрібні такі речі, як вогонь, спринт, прогулянка та зміна зброї, щоб вони були незареєстровані, а також розблокувати, щоб бути зареєстрованим.
Качка комуністична

0

Замість того, щоб зареєструватись, просто зробіть штат і потім перереєструйтесь.

In 'Secret Cloak Mode' function:
Grab the state of all bound keys- save somewhere
Unbind all keys
Re-register the keys/etc that we want to do.

In `Unsecret Cloak Mode`:
Unbind all keys
Rebind the state that we saved earlier.

Звичайно, розширити цю просту ідею було б те, що у вас можуть бути окремі стани для переміщення, і таке, а потім замість того, щоб продиктувати "Ну, ось усі речі, які я не можу зробити, перебуваючи в режимі секретного плаща, ось усе, що я можу робити в режимі таємного плаща. ".

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.