Masukan bersarang dalam sistem yang digerakkan oleh peristiwa

11

Saya menggunakan sistem penanganan input berbasis acara dengan acara dan delegasi. Sebuah contoh:

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

Namun, saya mulai bertanya-tanya tentang bagaimana menangani input 'bersarang'. Misalnya di Half-Life 2 (atau game Sumber mana pun, sungguh), Anda dapat mengambil item E. Ketika Anda telah mengambil item, Anda tidak dapat menembak dengan Left Mouse, tetapi Anda melemparkan objek. Anda masih bisa melompat Space.

(Saya mengatakan input bersarang adalah tempat Anda menekan tombol tertentu, dan tindakan yang dapat Anda lakukan berubah. Bukan menu.)

Tiga kasus tersebut adalah:

  • Mampu melakukan tindakan yang sama seperti sebelumnya (seperti melompat)
  • Tidak dapat melakukan tindakan yang sama (seperti menembak)
  • Melakukan tindakan yang sama sekali berbeda (seperti di NetHack, di mana menekan kunci pintu terbuka berarti Anda tidak bergerak, tetapi pilih arah untuk membuka pintu masuk)

Gagasan asli saya adalah hanya mengubahnya setelah input diterima:

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

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

Ini menderita sejumlah besar kopling, kode berulang, dan tanda-tanda desain buruk lainnya.

Saya kira pilihan lain adalah mempertahankan beberapa jenis sistem keadaan input - mungkin delegasi lain pada fungsi register untuk merestorasi sejumlah register / deregister ke tempat yang lebih bersih (dengan semacam tumpukan pada sistem input) atau mungkin array dari apa untuk menjaga dan apa yang tidak.

Saya yakin seseorang di sini pasti mengalami masalah ini. Bagaimana Anda menyelesaikannya?

tl; dr Bagaimana cara menangani input spesifik yang diterima setelah input spesifik lainnya dalam sistem acara?

Bebek Komunis
sumber

Jawaban:

7

dua opsi: jika kasus "input bersarang" paling banyak tiga, empat, saya hanya akan menggunakan flag. "Memegang benda? Tidak bisa menembak." Ada lagi yang overengineering itu.

Jika tidak, Anda dapat menyimpan tumpukan penanganan acara per-input-kunci.

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
}

Atau sesuatu untuk efek ini :)

Sunting : seseorang menyebutkan konstruksi if-else yang tidak dikelola. Apakah kita akan menggunakan data-driven penuh untuk kegiatan penanganan event input? Anda pasti bisa, tetapi mengapa?

Pokoknya, untuk itu:

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

Edit 2

Kylotan menyebutkan pemetaan kunci, yang merupakan fitur dasar yang harus dimiliki setiap game (pikirkan tentang aksesibilitas juga). Termasuk keymapping adalah cerita yang berbeda.

Mengubah perilaku tergantung pada kombinasi tombol tekan atau urutan terbatas. Saya mengabaikan bagian itu.

Perilaku terkait dengan logika game dan bukan input. Yang cukup jelas, kalau dipikir-pikir itu.

Karena itu, saya mengusulkan solusi berikut:

// //>

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

// //<

Ini bisa ditingkatkan dalam banyak hal oleh orang-orang yang lebih pintar dari saya, tetapi saya percaya itu juga merupakan titik awal yang baik.

Raine
sumber
Bekerja dengan baik untuk gim-gim sederhana - sekarang bagaimana Anda akan membuat kode kunci-mapper untuk ini? :) Itu saja bisa menjadi alasan yang cukup baik untuk menggunakan data-driven untuk masukan Anda.
Kylotan
@ Kylotan, itu pengamatan yang bagus, saya akan mengedit jawaban saya.
Raine
Ini jawaban yang bagus. Di sini, dapatkan hadiahnya: P
The Communist Duck
@The Communist Duck - terima kasih chap, semoga ini membantu.
Raine
11

Kami menggunakan sistem negara, seperti yang Anda sebutkan sebelumnya.

Kami akan membuat peta yang akan berisi semua kunci untuk negara tertentu dengan bendera yang memungkinkan melewati kunci yang sebelumnya dipetakan atau tidak. Ketika kami mengubah status, peta baru akan didorong atau peta sebelumnya akan muncul.

Contoh sederhana cepat dari status input adalah Default, In-Menu dan Magic-Mode. Default adalah tempat Anda berlarian dan bermain game. Dalam-Menu akan ketika Anda berada di menu mulai, atau ketika Anda telah membuka menu toko, menu jeda, layar opsi. In-Menu akan berisi flag no pass through karena ketika Anda menavigasi menu Anda tidak ingin karakter Anda bergerak. Di sisi lain, sangat mirip dengan contoh Anda dengan membawa item, Magic-Mode hanya akan memetakan kembali tombol tindakan / item untuk bukannya melemparkan mantra (kami juga akan mengikat itu untuk efek suara dan partikel tapi itu sedikit di luar pertanyaanmu).

Apa yang menyebabkan peta didorong dan muncul terserah Anda, dan saya juga akan dengan jujur ​​mengatakan bahwa kami memiliki acara 'jelas' untuk memastikan tumpukan peta tetap bersih, pemuatan tingkat menjadi waktu yang paling jelas (Cutscenes juga di waktu).

Semoga ini membantu.

TL; DR - Gunakan status dan peta input yang dapat Anda tekan dan / atau pop. Sertakan bendera untuk mengatakan apakah peta menghapus input sebelumnya atau tidak.

James
sumber
5
INI. Halaman dan halaman bersarang jika pernyataan adalah iblis.
michael.bartnett
+1. Ketika saya memikirkan input, saya selalu mengingat Acrobat Reader - Pilih Tool, Hand Tool, Marquee Zoom. IMO, menggunakan tumpukan mungkin berlebihan pada waktu-waktu tertentu. GEF menyembunyikan ini melalui AbstractTool . JHotDraw memiliki pandangan hierarki yang bagus tentang implementasi Alat mereka .
Stefan Hanke
2

Ini terlihat seperti kasus di mana warisan dapat menyelesaikan masalah Anda. Anda bisa memiliki kelas dasar dengan banyak metode yang menerapkan perilaku default. Anda kemudian dapat memperluas kelas ini dan mengganti beberapa metode. Beralih mode kemudian hanya masalah beralih dari implementasi saat ini.

Berikut ini beberapa kode semu

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. */ }

Ini mirip dengan apa yang diusulkan James.

subb
sumber
0

Saya tidak menulis kode persis dalam bahasa tertentu. Aku memberimu ide.

1) Petakan tindakan utama Anda ke acara Anda.

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

2) Tetapkan / Ubah acara Anda seperti yang diberikan di bawah ini

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() {
 ....
}

Biarkan aksi lompatan Anda tetap dipisahkan dengan peristiwa lain seperti lompat dan tembak.

Hindari if..else .. cek bersyarat di sini karena akan menyebabkan kode tidak terkelola.

di Razor
sumber
Ini sepertinya tidak membantu saya sama sekali. Ini memiliki masalah kopling tingkat tinggi dan tampaknya memiliki kode berulang.
Bebek Komunis
Hanya untuk mendapatkan pemahaman yang lebih baik - Dapatkah Anda menjelaskan apa yang "berulang" dalam hal ini dan di mana Anda melihat "kopling tingkat tinggi"?
inRazor
Jika Anda melihat contoh kode saya, saya harus menghapus semua tindakan dalam fungsi itu sendiri. Saya hardcoding semuanya ke fungsi itu sendiri - bagaimana jika dua fungsi ingin berbagi register / pembatalan yang sama? Saya harus menyalin kode ATAU harus memasangkannya. Juga akan ada sejumlah besar kode untuk menghapus semua tindakan yang tidak diinginkan. Terakhir, saya harus memiliki tempat yang 'mengingat' semua tindakan asli untuk menggantikannya.
Bebek Komunis
Bisakah Anda memberi saya dua fungsi aktual dari kode Anda (seperti fungsi jubah rahasia) dengan daftar register / deregister lengkap.
inRazor
Saya sebenarnya tidak memiliki kode untuk tindakan tersebut saat ini. Namun, hal itu secret cloakakan membutuhkan hal-hal seperti api, lari, berjalan, dan mengganti senjata agar tidak terdaftar, dan tidak terdaftar untuk didaftarkan.
Bebek Komunis
0

Alih-alih membatalkan pendaftaran, buat saja status lalu daftarkan kembali.

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.

Tentu saja, memperluas ide sederhana ini adalah bahwa Anda mungkin memiliki status terpisah untuk bergerak, dan semacamnya, dan alih-alih mendikte "Yah, inilah semua hal yang tidak dapat saya lakukan saat dalam Mode Jubah Rahasia, inilah semua yang saya bisa lakukan lakukan dalam Mode Jubah Rahasia. "

DeadMG
sumber