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


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.