อินพุตแบบซ้อนในระบบที่ขับเคลื่อนโดยเหตุการณ์


11

ฉันใช้ระบบจัดการการป้อนข้อมูลตามเหตุการณ์กับกิจกรรมและผู้รับมอบสิทธิ์ ตัวอย่าง:

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

อย่างไรก็ตามฉันเริ่มสงสัยเกี่ยวกับวิธีจัดการกับอินพุต 'ซ้อน' ยกตัวอย่างเช่นใน Half-Life 2 (หรือเกมใด ๆ ที่มาจริงๆ) 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'
...

สิ่งนี้ทนทุกข์ทรมานจากการแต่งงานกันจำนวนมากรหัสซ้ำ ๆ และสัญญาณการออกแบบที่ไม่ดีอื่น ๆ

ฉันเดาว่าตัวเลือกอื่นคือการรักษาระบบอินพุตบางประเภท - อาจเป็นตัวแทนอีกคนในฟังก์ชัน register เพื่อ refactor รีจิสเตอร์ register / deregister เหล่านั้นไปยังที่ที่สะอาดกว่า (ด้วย stack บางชนิดในระบบอินพุต) เพื่อรักษาและสิ่งที่ไม่ควรทำ

ฉันแน่ใจว่าบางคนที่นี่ต้องประสบปัญหานี้ คุณแก้ไขมันได้อย่างไร?

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 ที่ไม่สามารถจัดการได้ พวกเราจะไปกับข้อมูลเต็มรูปแบบสำหรับการจัดการเหตุการณ์การป้อนข้อมูล? คุณทำได้อย่างแน่นอน แต่ทำไม

อย่างไรก็ตามสำหรับ heck ของมัน:

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 นั่นเป็นข้อสังเกตที่ดีแน่นอนฉันจะแก้ไขคำตอบของฉัน
เรน

นี่คือคำตอบที่ดี ที่นี่มีความโปรดปราน: P
The คอมมิวนิสต์ Duck

@ The Communist Duck - ขอบคุณ chap หวังว่านี่จะช่วยได้
เรน

11

เราใช้ระบบของรัฐตามที่คุณพูดถึงมาก่อน

เราจะสร้างแผนที่ที่จะมีปุ่มทั้งหมดสำหรับสถานะที่ระบุด้วยการตั้งค่าสถานะที่จะอนุญาตให้ผ่านคีย์ที่แมปก่อนหน้านี้หรือไม่ เมื่อเราเปลี่ยนสถานะแผนที่ใหม่จะถูกผลักออกหรือแผนที่ก่อนหน้าจะถูกเปิดออก

ตัวอย่างง่ายๆของสถานะอินพุตจะเป็น Default, In-Menu และ Magic-Mode ค่าเริ่มต้นคือที่ที่คุณกำลังวิ่งเล่นและเล่นเกม ในเมนูจะเป็นเมื่อคุณอยู่ที่เมนูเริ่มหรือเมื่อคุณเปิดเมนูร้านค้าเมนูหยุดชั่วคราวหน้าจอตัวเลือก ในเมนูจะมีการไม่มีการผ่านธงเนื่องจากในขณะที่คุณนำทางเมนูคุณไม่ต้องการให้ตัวละครของคุณเคลื่อนไหว ในอีกด้านหนึ่งเหมือนกับตัวอย่างของคุณที่มีการบรรทุกไอเท็ม Magic-Mode จะทำการแมปปุ่มแอ็คชั่น / ไอเท็มเพื่อใช้คาถาแทนคาถาแทน (เราจะผูกมันกับเอฟเฟกต์ของเสียงและอนุภาค คำถามของคุณ).

อะไรเป็นสาเหตุของแผนที่ที่จะผลักดันและแตกขึ้นอยู่กับคุณและฉันก็จะพูดอย่างตรงไปตรงมาว่าเรามีเหตุการณ์ 'ชัดเจน' เพื่อให้แน่ใจว่าแผนที่กองซ้อนนั้นสะอาดอยู่เสมอระดับโหลดเป็นเวลาที่ชัดเจนที่สุด (Cutscenes ครั้ง)

หวังว่านี่จะช่วยได้

TL; DR - ใช้สถานะและแผนที่อินพุตที่คุณสามารถกดและ / หรือป๊อป รวมถึงการตั้งค่าสถานะเพื่อบอกว่าแผนที่ลบข้อมูลก่อนหน้านี้ทั้งหมดหรือไม่


5
นี้. หน้าและหน้าของซ้อนกันถ้างบเป็นปีศาจ
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() {
 ....
}

ให้การกระโดดของคุณยังคงถูกแยกออกจากเหตุการณ์อื่นเช่นการกระโดดและการยิง

หลีกเลี่ยงถ้า .. ตรวจสอบตามเงื่อนไข .. ที่นี่เพราะจะทำให้รหัสไม่สามารถจัดการได้


ดูเหมือนจะไม่ช่วยฉันเลย มันมีปัญหาของการมีเพศสัมพันธ์ในระดับสูงและดูเหมือนว่าจะมีรหัสซ้ำ
The Duck Communist

เพียงเพื่อให้ได้ความเข้าใจที่ดีขึ้น - คุณสามารถอธิบายสิ่งที่ "ซ้ำ" ในสิ่งนี้และคุณเห็น "การมีเพศสัมพันธ์ในระดับสูง" อยู่ที่ไหน
inRazor

หากคุณเห็นตัวอย่างรหัสของฉันฉันต้องลบการกระทำทั้งหมดในฟังก์ชั่นของตัวเอง ฉันกำลังฮาร์ดโค้ดทั้งหมดให้กับฟังก์ชั่นของตัวเอง - จะเกิดอะไรขึ้นถ้าสองฟังก์ชั่นต้องการแชร์รีจิสเตอร์ / รีจิสเตอร์เดียวกัน? ฉันจะต้องทำซ้ำรหัสหรือต้องจับคู่พวกเขา นอกจากนี้ยังมีรหัสจำนวนมากเพื่อลบการกระทำที่ไม่พึงประสงค์ทั้งหมด สุดท้ายนี้ฉันต้องมีที่ที่จำการกระทำเดิมทั้งหมดเพื่อแทนที่
The Duck Communist

คุณช่วยให้สองฟังก์ชั่นที่เกิดขึ้นจริงจากรหัสของคุณ (เช่นฟังก์ชั่นเสื้อคลุมลับ) กับงบลงทะเบียน / ลงทะเบียนที่สมบูรณ์
inRazor

ตอนนี้ฉันยังไม่มีรหัสสำหรับการกระทำเหล่านั้น อย่างไรก็ตามสิ่งsecret cloakนั้นจะต้องมีสิ่งต่าง ๆ เช่นไฟการวิ่งการเดินและการเปลี่ยนอาวุธที่จะไม่ลงทะเบียนและไม่ได้ลงทะเบียน
The Duck Communist

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.

แน่นอนว่าการขยายแนวคิดง่าย ๆ นี้อาจเป็นเพราะคุณอาจมีรัฐแยกต่างหากสำหรับการเคลื่อนไหวและเช่นนั้นและแทนที่จะบอกว่า "เอาล่ะนี่คือทุกสิ่งที่ฉันไม่สามารถทำได้ขณะอยู่ในโหมด Secret Cloak นี่คือทุกสิ่งที่ฉันทำได้ทำในโหมด Secret Cloak ".

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.