एक इवेंट-संचालित सिस्टम में नेस्टेड इनपुट


11

मैं घटनाओं और प्रतिनिधियों के साथ एक इवेंट-आधारित इनपुट हैंडलिंग सिस्टम का उपयोग कर रहा हूं। एक उदाहरण:

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

हालांकि, मैं 'नेस्टेड' इनपुट से निपटने के तरीके के बारे में सोचना शुरू कर दिया। हाफ-लाइफ 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'
...

यह बड़ी मात्रा में युग्मन, दोहराव कोड और अन्य खराब डिजाइन संकेतों से ग्रस्त है।

मुझे लगता है कि अन्य विकल्प कुछ प्रकार के इनपुट राज्य प्रणाली को बनाए रखना है - शायद रजिस्टर फ़ंक्शन पर एक अन्य प्रतिनिधि उन कई रजिस्टर / डेरेगिस्टर को एक क्लीनर स्थान पर रिफ्लेक्टर करने के लिए (इनपुट सिस्टम पर किसी प्रकार के ढेर के साथ) या शायद क्या है रखने के लिए और क्या नहीं करने के लिए।

मुझे यकीन है कि यहाँ किसी को इस समस्या का सामना करना पड़ा होगा। आपने इसे कैसे हल किया है?

एक घटना प्रणाली में एक और विशिष्ट इनपुट के बाद प्राप्त विशिष्ट इनपुट से मैं कैसे निपट सकता हूं?

जवाबों:


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
}

या इस प्रभाव के लिए कुछ :)

संपादित करें : किसी ने असहनीय का उल्लेख किया है, अन्यथा निर्माण करता है। क्या हम रूटीन हैंडलिंग से संबंधित इनपुट इवेंट के लिए पूर्ण डेटा-चालित जा रहे हैं? आप निश्चित रूप से कर सकते हैं, लेकिन क्यों?

वैसे भी, इस के लिए:

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

संपादित करें २

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

// //<

लोगों द्वारा मुझसे बेहतर तरीके से इसे कई तरीकों से बेहतर बनाया जा सकता है, लेकिन मेरा मानना ​​है कि यह एक अच्छा शुरुआती बिंदु है।


सरल गेम के लिए ठीक काम करता है - अब आप इसके लिए एक की-मैपर को कैसे कोड करने जा रहे हैं? :) यह अकेले आपके इनपुट के लिए डेटा-संचालित होने के लिए एक अच्छा पर्याप्त कारण हो सकता है।
काइलोटन

@ किलोटन, यह वास्तव में एक अच्छा अवलोकन है, मैं अपना उत्तर संपादित करने जा रहा हूं।
रैन

यह एक बेहतरीन जवाब है। यहाँ, इनाम: पी
द कम्युनिस्ट डक

@ द कम्युनिस्ट डक - धन्यवाद चप, आशा है कि यह मदद करता है।
रीन

11

जैसा कि आपने पहले उल्लेख किया था, हमने एक राज्य प्रणाली का उपयोग किया।

हम एक ऐसा नक्शा बनाएंगे जिसमें एक ध्वज के साथ एक विशिष्ट राज्य के लिए सभी कुंजियाँ होंगी जो पहले से मैप की गई कुंजियों से गुजरेंगी या नहीं। जब हम बदलते हैं तो नए नक्शे को धक्का दिया जाएगा या पिछले नक्शे को बंद कर दिया जाएगा।

इनपुट राज्यों का त्वरित सरल उदाहरण डिफ़ॉल्ट, इन-मेनू और मैजिक-मोड होगा। डिफ़ॉल्ट वह जगह है जहाँ आप इधर-उधर भाग रहे हैं और खेल खेल रहे हैं। इन-मेन्यू तब होगा जब आप स्टार्ट मेन्यू पर हों, या जब आपने एक शॉप मेन्यू खोला हो, पॉज़ मेनू, एक विकल्प स्क्रीन। इन-मेनू में ध्वज के माध्यम से कोई पास नहीं होगा क्योंकि जब आप एक मेनू नेविगेट करते हैं तो आप नहीं चाहते कि आपका चरित्र घूम रहा हो। दूसरी तरफ, आइटम ले जाने के साथ आपका उदाहरण बहुत पसंद है, मैजिक-मोड बस एक्शन मंत्रों को हटाने के लिए एक्शन / आइटम का उपयोग करेगा। हम ध्वनि और कण प्रभावों के लिए भी टाई करेंगे लेकिन यह थोड़ा परे है आपका प्रश्न)।

क्या कारण है कि नक्शे को धक्का दिया गया है और आप पर निर्भर है, और मैं भी ईमानदारी से कहूंगा कि क्या हमारे पास कुछ 'स्पष्ट' घटनाएं हैं ताकि यह सुनिश्चित हो सके कि नक्शा स्टैक को साफ रखा गया है, स्तर लोड हो रहा है सबसे स्पष्ट समय (Cutscenes) बार)।

उम्मीद है की यह मदद करेगा।

TL, DR - राज्यों और एक इनपुट मानचित्र का उपयोग करें जिसे आप धक्का और / या पॉप कर सकते हैं। यह कहने के लिए एक ध्वज शामिल करें कि क्या मानचित्र पूरी तरह से पिछले इनपुट को हटाता है या नहीं।


5
यह। पृष्ठ और नेस्टेड के पृष्ठ यदि कथन शैतान हैं।
michael.bartnett

+1। जब मैं इनपुट पर सोचता हूं, तो मेरे दिमाग में हमेशा एक्रोबैट रीडर होता है - टूल, हैंड टूल, मार्की ज़ूम चुनें। 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() {
 ....
}

अपने कूदने की क्रिया को कूद और आग जैसी अन्य घटनाओं से अलग रहने दें।

से बचें अगर .. यहाँ .. सशर्त जाँच यहाँ के रूप में यह असहनीय कोड के लिए नेतृत्व करेंगे।


यह मुझे मदद करने के लिए बिल्कुल नहीं लगता है। इसमें उच्च स्तर के युग्मन की समस्या है और लगता है कि पुनरावृत्ति कोड है।
कम्युनिस्ट डक

बस एक बेहतर समझ प्राप्त करने के लिए - क्या आप बता सकते हैं कि इसमें "दोहराव" क्या है और आपको "युग्मन के उच्च स्तर" दिखाई देते हैं।
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 Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.