이벤트 중심 시스템에서 중첩 된 입력


11

이벤트 및 대리자와 함께 이벤트 기반 입력 처리 시스템을 사용하고 있습니다. 예를 들면 :

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

그러나 '중첩 된'입력을 처리하는 방법에 대해 궁금해하기 시작했습니다. 예를 들어 Half-Life 2 (또는 실제로는 모든 소스 게임)에서을 사용하여 항목을 선택할 수 있습니다 E. 아이템을 집어 들었을 때는로 발사 할 수 Left Mouse없지만 대신 물건을 던집니다. 당신은 여전히으로 점프 할 수 있습니다 Space.

(중첩 입력은 특정 키를 누르는 곳이며 수행 할 수있는 작업은 메뉴가 아닙니다.)

세 가지 경우는 다음과 같습니다.

  • 이전과 동일한 동작을 수행 할 수있는 경우 (예 : 점프)
  • 같은 행동을 할 수없는 것
  • 다른 행동을 완전히하는 것

내 원래 아이디어는 입력을받은 후에 변경하는 것이 었습니다.

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

두 가지 옵션 : "중첩 입력"사례가 최대 3-4 인 경우 플래그 만 사용합니다. "물건을 잡으시겠습니까? 발사 할 수 없습니다." 다른 것은 그것을 과도하게 설계하고 있습니다.

그렇지 않으면 이벤트 핸들러의 입력 키당 스택을 유지할 수 있습니다.

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, 그것은 실제로 좋은 관찰입니다. 나는 대답을 편집 할 것입니다.
— Raine

이것은 좋은 대답입니다. 여기에 현상금이 있습니다 : P
— 공산주의 오리

@ The Communist Duck-고맙습니다. 도움이 되길 바랍니다.
— Raine

11

앞에서 언급했듯이 상태 시스템을 사용했습니다.

이전에 매핑 된 키를 통과 할 수 있는지 여부를 나타내는 플래그와 함께 특정 상태에 대한 모든 키를 포함하는 맵을 만듭니다. 상태를 변경하면 새 맵이 푸시되거나 이전 맵이 튀어 나옵니다.

입력 상태의 간단한 예는 기본, 메뉴 내 및 매직 모드입니다. 기본은 당신이 주위를 실행하고 게임을하는 곳입니다. 메뉴내는 시작 메뉴에 있거나 상점 메뉴, 일시 중지 메뉴, 옵션 화면을 열었을 때입니다. 메뉴를 탐색 할 때 캐릭터가 움직이지 않기 때문에 메뉴 내에는 통과 없음 플래그가 포함됩니다. 다른 한편으로, 아이템을 운반하는 예제와 마찬가지로 Magic-Mode는 단순히 액션 / 아이템 사용 키를 다시 매핑하여 주문을 시전합니다 (우리는 사운드 및 파티클 효과와도 관련이 있지만 조금 넘어 있습니다) 귀하의 질문).

지도가 밀리고 터지는 원인은 당신에게 달려 있으며, 솔직히 말해서 맵 스택을 깨끗하게 유지하고 레벨 로딩이 가장 분명한 시간이라는 것을 분명히하기 위해 특정 '명확한'이벤트가있었습니다 (Cutscenes 및 타임스).

도움이 되었기를 바랍니다.

TL; DR-푸시 및 / 또는 팝업 할 수있는 상태 및 입력 맵을 사용합니다. 지도가 이전 입력을 완전히 제거하는지 여부를 나타내는 플래그를 포함합니다.


5
이. 중첩 된 if 문의 페이지와 페이지가 악마입니다.
— michael.bartnett

+1. 입력을 생각할 때 항상 도구, 수공구, 선택 윤곽 확대 등 Acrobat Reader를 염두에 둡니다. 스택을 사용하는 IMO는 때때로 과도 할 수 있습니다. GEF는 이것을 AbstractTool을 통해 숨 깁니다 . JHotDraw는 툴 구현에 대한 멋진 계층 구조 뷰를 가지고 있습니다.
— Stefan Hanke

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

코드 예제를 보면 함수 자체의 모든 작업을 제거해야합니다. 함수 자체에 모두 하드 코딩하고 있습니다. 두 함수가 동일한 레지스터 / 등록 취소를 공유하려면 어떻게해야합니까? 코드를 복제하거나 커플 링해야합니다. 또한 원치 않는 모든 작업을 제거하는 많은 양의 코드가 있습니다. 마지막으로, 나는 그것을 대체하기 위해 모든 원래의 행동을 '기억'하는 장소가 있어야합니다.
— 공산주의 오리

완전한 register / deregister 문을 사용하여 코드에서 비밀 클로킹 함수와 같은 두 가지 실제 함수를 제공 할 수 있습니까?
— 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.

물론,이 간단한 아이디어를 확장하는 것은 당신이 움직일 수있는 별도의 상태를 가질 수 있다는 것입니다. 그리고 "음, 비밀 망토 모드에서 할 수없는 모든 것이 있습니다 . 여기 내가 할 수있는 모든 것이 있습니다." 은밀한 망토 모드에서 할 수 있습니다. "

당사 사이트를 사용함과 동시에 당사의 쿠키 정책과 개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.