Bagaimana merancang menu konteks berdasarkan objek apa pun itu?

21

Saya mencari solusi untuk perilaku "Opsi Klik Kanan".

Pada dasarnya setiap dan setiap item dalam game, ketika diklik kanan, dapat menampilkan serangkaian opsi berdasarkan objek apa pun itu.

Contoh klik kanan untuk skenario yang berbeda :

Inventaris: Helm menunjukkan opsi (Lengkapi, Gunakan, Jatuhkan, Deskripsi)

Bank: Helm menunjukkan opsi (Ambil 1, Ambil X, Ambil Semua, Deskripsi)

Lantai: Helm menunjukkan opsi (Ambil, Jalan Di Sini, Deskripsi)

Jelas setiap opsi entah bagaimana menunjuk ke metode tertentu yang melakukan apa yang dikatakan. Ini adalah bagian dari masalah yang saya coba selidiki. Dengan begitu banyak opsi potensi untuk satu item, bagaimana kelas saya dirancang sedemikian rupa agar tidak menjadi sangat berantakan?

  • Saya sudah berpikir tentang warisan tetapi itu bisa sangat panjang lebar dan rantai bisa sangat besar.
  • Saya sudah berpikir tentang menggunakan antarmuka, tetapi ini mungkin akan membatasi saya sedikit karena saya tidak akan dapat memuat data item dari file Xml dan menempatkannya ke dalam kelas "Item" umum.

Saya mendasarkan hasil akhir yang saya inginkan pada permainan yang disebut Runescape. Setiap objek dapat diklik kanan dalam permainan dan tergantung pada apa itu, dan di mana itu (inventaris, lantai, bank dll) menampilkan serangkaian opsi yang tersedia untuk pemain untuk berinteraksi.

Bagaimana saya bisa mencapai ini? Pendekatan apa yang harus saya ambil untuk pertama-tama, memutuskan opsi mana yang HARUS ditampilkan dan setelah diklik, cara memanggil metode yang sesuai.

Saya menggunakan C # dan Unity3D, tetapi setiap contoh yang diberikan tidak harus terkait dengan salah satu dari mereka karena saya mencari pola yang bertentangan dengan kode aktual.

Setiap bantuan sangat dihargai dan jika saya belum jelas dalam pertanyaan saya atau hasil yang diinginkan, silakan kirim komentar dan saya akan cenderung ASAP.

Inilah yang saya coba sejauh ini:

  • Saya sebenarnya telah berhasil mengimplementasikan kelas "Item" umum yang menampung semua nilai untuk berbagai jenis item (serangan ekstra, pertahanan ekstra, biaya dll ...). Variabel-variabel ini diisi oleh data dari file Xml.
  • Saya telah berpikir tentang menempatkan setiap metode interaksi yang mungkin dalam kelas Item tapi saya pikir ini adalah bentuk yang berantakan dan buruk. Saya mungkin telah mengambil pendekatan yang salah untuk menerapkan sistem semacam ini dengan hanya menggunakan satu kelas dan tidak sub-klasifikasi ke item yang berbeda, tetapi itu satu-satunya cara saya bisa memuat data dari Xml dan menyimpannya di kelas.
  • Alasan saya memilih untuk memuat semua item saya dari file Xml adalah karena permainan ini memiliki kemungkinan untuk lebih dari 40.000 item. Jika matematika saya benar, kelas untuk setiap item adalah banyak kelas.
Mike Hunt
sumber
Melihat daftar perintah Anda, dengan pengecualian "Equip," sepertinya semuanya generik dan berlaku terlepas dari apa itemnya - ambil, jatuhkan,
uraikan
Jika suatu item tidak dapat diperdagangkan, alih-alih "Drop" itu dapat memiliki "Hancurkan"
Mike Hunt
Sejujurnya, banyak game menyelesaikan ini menggunakan DSL - bahasa skrip khusus untuk permainan.
corsiKa
1
+1 untuk memodelkan game Anda setelah RuneScape. Saya suka game itu.
Zenadix

Jawaban:

23

Seperti halnya segala sesuatu dalam pengembangan perangkat lunak, tidak ada solusi yang ideal. Hanya solusi yang ideal untuk Anda dan proyek Anda. Berikut beberapa yang bisa Anda gunakan.

Opsi 1: Model prosedural

The kuno usang metode lama-sekolah.

Semua item adalah tipe data sederhana-lama-bodoh tanpa metode apa pun tetapi banyak atribut publik yang mewakili semua properti yang dimiliki suatu item, termasuk beberapa boolean flags seperti isEdible, isEquipabledll. Yang menentukan entri menu konteks apa yang tersedia untuk itu (mungkin Anda juga bisa lakukan tanpa flag-flag ini ketika Anda dapat menurunkannya dari nilai atribut lainnya). Memiliki beberapa metode seperti Eat, Equipdll. Di kelas pemain Anda yang mengambil item dan yang memiliki semua logika untuk memprosesnya sesuai dengan nilai atribut.

Opsi 2: Model berorientasi objek

Ini lebih merupakan solusi OOP-oleh-the-book yang didasarkan pada warisan dan polimorfisme.

Memiliki kelas dasar Itemdari mana barang-barang lain suka EdibleItem, EquipableItemdll mewarisi. Kelas dasar harus memiliki metode publik GetContextMenuEntriesForBank, GetContextMenuEntriesForFloordll. Yang mengembalikan daftar ContextMenuEntry. Setiap kelas pewarisan akan mengganti metode ini untuk mengembalikan entri menu konteks yang sesuai untuk jenis item ini. Itu juga bisa memanggil metode yang sama dari kelas dasar untuk mendapatkan beberapa entri default yang berlaku untuk semua jenis barang. Itu ContextMenuEntryakan menjadi kelas dengan metode Performyang kemudian memanggil metode yang relevan dari Item yang membuatnya (Anda bisa menggunakan delegasi untuk ini).

Mengenai masalah Anda dengan menerapkan pola ini saat membaca data dari file XML: Pertama-tama periksa simpul XML untuk setiap item untuk menentukan jenis item, kemudian gunakan kode khusus untuk setiap jenis untuk membuat turunan dari sub-kelas yang sesuai.

Opsi 3: Model berbasis komponen

Pola ini menggunakan komposisi alih-alih warisan dan lebih dekat dengan cara kerja Unity lainnya. Bergantung pada bagaimana Anda menyusun game Anda, ada kemungkinan / bermanfaat untuk menggunakan sistem komponen Unity untuk ini ... atau tidak, jarak tempuh Anda mungkin berbeda-beda.

Setiap objek dari kelas Itemakan memiliki daftar komponen seperti Equipable, Edible, Sellable, Drinkable, dll Item dapat memiliki satu atau tidak masing-masing komponen (misalnya, helm yang terbuat dari cokelat akan baik Equipabledan Edible, dan jika tidak plot-kritis item pencarian juga Sellable). Logika pemrograman yang khusus untuk komponen diimplementasikan dalam komponen itu. Ketika pengguna mengklik kanan pada suatu item, komponen-komponen dari item itu diulang dan entri menu konteks ditambahkan untuk setiap komponen yang ada. Ketika pengguna memilih salah satu entri ini, komponen yang menambahkan entri itu memproses opsi.

Anda dapat mewakili ini dalam file XML Anda dengan memiliki sub-node untuk setiap komponen. Contoh:

   <item>
      <name>Chocolate Helmet</name>
      <sprite>helmet-chocolate.png</sprite>
      <description>Protects you from enemies and from starving</description>
      <edible>
          <taste>sweet</taste>
          <calories>2560</calories>
      </edible>
      <equipable>
          <slot>head</slot>
          <def>20</def>
      </equipable>
      <sellable>
          <value>120</value>
      </sellable>
   </item>
Philipp
sumber
Terima kasih atas penjelasan Anda yang berharga dan waktu yang Anda ambil untuk menjawab pertanyaan saya. Meskipun saya belum memutuskan metode apa yang akan saya gunakan, saya sangat menghargai metode implementasi alternatif yang telah Anda berikan. Saya akan duduk dan berpikir tentang metode apa yang akan bekerja lebih baik untuk saya dan pergi dari sana. Terima kasih :)
Mike Hunt
@ MikeHunt Model daftar komponen jelas merupakan sesuatu yang harus Anda selidiki, karena berfungsi baik dengan memuat definisi item dari file.
user253751
@ imibis itulah yang akan saya coba dulu karena upaya awal saya mirip dengan itu. Terima kasih :)
Mike Hunt
Jawaban lama, tetapi apakah ada dokumentasi tentang cara menerapkan model "daftar komponen"?
Jeff
@ Jeff Jika Anda ingin menerapkan pola ini di game Anda dan memiliki pertanyaan tentang caranya, silakan kirim pertanyaan baru.
Philipp
9

Jadi, Mike Hunt, pertanyaan Anda sangat menarik bagi saya, saya memutuskan untuk menerapkan solusi lengkap. Setelah tiga jam mencoba hal yang berbeda, saya berakhir dengan solusi selangkah demi selangkah ini:

(Harap dicatat, bahwa ini BUKAN kode yang sangat bagus, jadi saya akan menerima suntingan)

Membuat Panel Konten

(Panel ini akan menjadi wadah untuk tombol menu konteks kami)

  • Membuat baru UI Panel
  • Setel anchorke kiri bawah
  • Setel widthke 300 (sesuai keinginan)
  • Tambahkan ke panel, komponen baru Vertical Layout Groupdan atur Child Alignmentke tengah-atas, Child Force Expandke lebar (bukan tinggi)
  • Tambahkan ke panel, komponen baru Content Size Fitterdan setel Vertical Fitke Ukuran Min
  • Simpan itu sebagai cetakan

(Pada titik ini Panel kami akan menyusut menjadi satu garis. Itu normal. Panel ini akan menerima tombol sebagai anak-anak, menyelaraskannya secara vertikal dan meregangkan ke ketinggian isi ringkasan)

Membuat Tombol Sampel

(Tombol ini akan dipakai dan disesuaikan untuk menampilkan item menu konteks)

  • Buat Tombol UI baru
  • Setel anchorke kiri atas
  • Tambahkan ke tombol komponen baru Layout Element, atur Min Heightke 30, Preferred Heightke 30
  • Simpan itu sebagai cetakan

Membuat skrip ContextMenu.cs

(Skrip ini memiliki metode yang membuat dan menampilkan Menu Konteks)

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

[System.Serializable]
public class ContextMenuItem
{
    // this class - just a box to some data

    public string text;             // text to display on button
    public Button button;           // sample button prefab
    public Action<Image> action;    // delegate to method that needs to be executed when button is clicked

    public ContextMenuItem(string text, Button button, Action<Image> action)
    {
        this.text = text;
        this.button = button;
        this.action = action;
    }
}

public class ContextMenu : MonoBehaviour
{
    public Image contentPanel;              // content panel prefab
    public Canvas canvas;                   // link to main canvas, where will be Context Menu

    private static ContextMenu instance;    // some kind of singleton here

    public static ContextMenu Instance
    {
        get
        {
            if(instance == null)
            {
                instance = FindObjectOfType(typeof(ContextMenu)) as ContextMenu;
                if(instance == null)
                {
                    instance = new ContextMenu();
                }
            }
            return instance;
        }
    }

    public void CreateContextMenu(List<ContextMenuItem> items, Vector2 position)
    {
        // here we are creating and displaying Context Menu

        Image panel = Instantiate(contentPanel, new Vector3(position.x, position.y, 0), Quaternion.identity) as Image;
        panel.transform.SetParent(canvas.transform);
        panel.transform.SetAsLastSibling();
        panel.rectTransform.anchoredPosition = position;

        foreach(var item in items)
        {
            ContextMenuItem tempReference = item;
            Button button = Instantiate(item.button) as Button;
            Text buttonText = button.GetComponentInChildren(typeof(Text)) as Text;
            buttonText.text = item.text;
            button.onClick.AddListener(delegate { tempReference.action(panel); });
            button.transform.SetParent(panel.transform);
        }
    }
}
  • Lampirkan skrip ini ke kanvas dan mengisi bidang. Seret n-drop ContentPanelprefab ke slot yang sesuai, dan seret Kanvas itu sendiri ke slot Canvas.

Membuat skrip ItemController.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class ItemController : MonoBehaviour
{
    public Button sampleButton;                         // sample button prefab
    private List<ContextMenuItem> contextMenuItems;     // list of items in menu

    void Awake()
    {
        // Here we are creating and populating our future Context Menu.
        // I do it in Awake once, but as you can see, 
        // it can be edited at runtime anywhere and anytime.

        contextMenuItems = new List<ContextMenuItem>();
        Action<Image> equip = new Action<Image>(EquipAction);
        Action<Image> use = new Action<Image>(UseAction);
        Action<Image> drop = new Action<Image>(DropAction);

        contextMenuItems.Add(new ContextMenuItem("Equip", sampleButton, equip));
        contextMenuItems.Add(new ContextMenuItem("Use", sampleButton, use));
        contextMenuItems.Add(new ContextMenuItem("Drop", sampleButton, drop));
    }

    void OnMouseOver()
    {
        if(Input.GetMouseButtonDown(1))
        {
            Vector3 pos = Camera.main.WorldToScreenPoint(transform.position);
            ContextMenu.Instance.CreateContextMenu(contextMenuItems, new Vector2(pos.x, pos.y));
        }

    }

    void EquipAction(Image contextPanel)
    {
        Debug.Log("Equipped");
        Destroy(contextPanel.gameObject);
    }

    void UseAction(Image contextPanel)
    {
        Debug.Log("Used");
        Destroy(contextPanel.gameObject);
    }

    void DropAction(Image contextPanel)
    {
        Debug.Log("Dropped");
        Destroy(contextPanel.gameObject);
    }
}
  • Buat objek sampel dalam adegan (yaitu Cube), tempatkan agar dapat dilihat oleh kamera dan lampirkan script ini padanya. Seret n-drop sampleButtonprefab ke slot yang sesuai.

Sekarang, coba jalankan. Ketika Anda mengklik kanan objek, menu konteks akan muncul, diisi dengan daftar yang kami buat. Menekan tombol akan mencetak ke konsol beberapa teks, dan menu konteks akan dihancurkan.

Kemungkinan peningkatan:

  • bahkan lebih generik!
  • manajemen memori yang lebih baik (tautan kotor, tidak merusak panel, menonaktifkan)
  • beberapa barang mewah

Contoh proyek (Unity Personal 5.2.0, Plugin VisualStudio): https://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp=sharing

Exerion
sumber
Wow terima kasih banyak telah meluangkan waktu Anda untuk mengimplementasikan ini. Saya akan menguji implementasi Anda segera setelah saya kembali ke komputer saya. Saya pikir untuk tujuan penjelasan, saya akan menerima jawaban Philipp berdasarkan berbagai penjelasan untuk metode yang dapat digunakan. Saya akan meninggalkan jawaban Anda di sini karena saya percaya itu sangat berharga dan orang-orang yang melihat pertanyaan ini di masa depan akan memiliki implementasi yang aktual serta beberapa metode untuk menerapkan hal semacam ini dalam permainan. Terima kasih banyak dan dilakukan dengan baik. Saya juga memilih ini :)
Mike Hunt
1
Sama-sama. Alangkah baiknya jika jawaban ini akan membantu seseorang.
Exerion