Nesne ne olursa olsun bağlam menüleri nasıl tasarlanır?


21

"Sağ Tıklama Seçenekleri" davranışı için bir çözüm arıyorum.

Temel olarak bir oyundaki her öğe, sağ tıklandığında nesnenin ne olduğuna bağlı olarak bir dizi seçenek görüntüleyebilir.

Farklı senaryolar için sağ tıklama örnekleri :

Envanter: Kask seçenekleri gösterir (Donat, Kullan, Bırak, Açıklama)

Banka: Kask seçenekleri gösterir (1'i al, X'i al, Tümünü al, Açıklama)

Kat: Kask seçenekleri gösterir (Al, Yürü, Açıklama)

Her seçenek bir şekilde söylenenleri yapan belirli bir yönteme işaret ediyor. Bu çözmeye çalıştığım sorunun bir parçası. Tek bir eşya için pek çok potens seçeneği ile, derslerimin aşırı derecede dağınık olmayacak şekilde nasıl tasarlanmasını sağlayacağım?

  • Kalıtım hakkında düşündüm ama bu gerçekten çok uzun sürebilir ve zincir çok büyük olabilir.
  • Arayüzleri kullanmayı düşündüm, ancak bir Xml dosyasından öğe verilerini yüklemek ve genel bir "Item" sınıfına yerleştirmek mümkün olmayacağından, bu muhtemelen beni biraz kısıtlayacaktır.

İstediğim son sonucu Runescape adlı bir oyuna dayandırıyorum. Her nesne oyunda sağ tıklanabilir ve ne olduğuna ve nerede olduğuna bağlı olarak (envanter, zemin, banka vb.) Oyuncunun etkileşimde bulunabileceği farklı seçenekler sunar.

Bunu nasıl başarabilirim? Her şeyden önce hangi yaklaşımı benim izlemeliyim, hangi seçeneklerin görüntülenmesi ve tıklandığında, ilgili yöntemin nasıl çağrılacağına karar vermeliyim.

C # ve Unity3D kullanıyorum, ancak sağlanan herhangi bir örnek gerçek kodun aksine bir desen sonra ben bunlardan biri ile ilgili olmak zorunda değildir.

Herhangi bir yardım çok takdir ve sorum veya istenen sonuçlarda net değilse, lütfen bir yorum gönderin ve en kısa sürede buna yönelirim.

İşte şimdiye kadar denedim:

  • Aslında farklı tür öğeleri (ekstra saldırı, ekstra savunma, maliyet vb ...) için tüm değerleri tutan genel bir "Öğe" sınıfı uygulamayı başardı. Bu değişkenler bir Xml dosyasındaki verilerle doldurulur.
  • Her olası etkileşim yöntemini Item sınıfının içine yerleştirmeyi düşündüm, ancak bunun inanılmaz derecede dağınık ve zayıf bir form olduğunu düşünüyorum. Muhtemelen sadece bir sınıfı kullanarak ve farklı öğelere alt sınıflandırma kullanarak bu tür bir sistem uygulamak için yanlış bir yaklaşım aldım, ama bu bir Xml veri yüklemek ve sınıfta saklamak tek yolu.
  • Tüm öğelerimi bir Xml dosyasından yüklemeyi seçmemizin nedeni, bu oyunun 40.000'den fazla ürüne sahip olması. Benim matematik doğru ise, her madde için bir sınıf çok sınıf.

"Equip" hariç olmak üzere komutlar listenize baktığınızda, hepsi genel gibi görünüyor ve öğenin ne olduğuna bakılmaksızın uygulanır - al, bırak, açıklama, buraya
taşı

Bir eşya takas edilemezse, "Bırak" yerine "Yok Et" olabilir
Mike Hunt

Açıkçası, birçok oyun bunu bir DSL kullanarak çözer - oyuna özgü özel bir betik dili.
corsiKa

1
RuneScape'ten sonra oyununuzu modellemek için +1. Bu oyunu çok seviyorum.
Zenadix

Yanıtlar:


22

Yazılım geliştirmedeki her şeyde olduğu gibi, ideal bir çözüm yoktur. Sadece sizin ve projeniz için ideal çözüm. İşte kullanabileceğiniz bazı.

Seçenek 1: Usul modeli

Antik eskimiş eski okul yöntemi.

Tüm öğeler dilsiz düz eski verileri herhangi yöntemlerle olmadan türlerini ama gibi bazı boolean bayrakları içeren bir öğe olabilir tüm özellikleri temsil kamu nitelikler, bir sürü vardır isEdible, isEquipableiçerik menüsü girişleri belki de olabilir (bunun için ne gibi belirlemek vb diğer bayrakların değerlerinden türetebildiğiniz zaman bu bayraklar olmadan yapın). Oyuncu sınıfınızda, bir öğeyi alan ve öznitelik değerlerine göre işlemek için tüm mantığa sahip Eat, Equipvb. Gibi bazı yöntemler kullanın.

Seçenek 2: Nesneye yönelik model

Bu daha çok kalıtım ve polimorfizme dayanan OOP-by-the-book çözümü.

Bir baz sınıf var Itemdiğer öğeler gibi hangi EdibleItem, EquipableItemvb devralır. Temel sınıfın GetContextMenuEntriesForBank, GetContextMenuEntriesForFloorlistesini döndüren genel bir yöntemi vb. Olmalıdır ContextMenuEntry. Her devralma sınıfı, bu öğe türüne uygun bağlam menüsü girdilerini döndürmek için bu yöntemleri geçersiz kılar. Ayrıca, herhangi bir öğe türü için geçerli bazı varsayılan girişleri almak için temel sınıfla aynı yöntemi çağırabilir. Bu ContextMenuEntry, Performdaha sonra onu oluşturan Öğeden ilgili yöntemi çağıran bir yöntem içeren bir sınıf olacaktır (bunun için bir temsilci kullanabilirsiniz ).

XML dosyasından veri okurken bu kalıbı uygulamadaki sorunlarınızla ilgili olarak: Önce öğenin türünü belirlemek için her öğenin XML düğümünü inceleyin, ardından uygun alt sınıfın bir örneğini oluşturmak için her tür için özel kod kullanın.

Seçenek 3: Bileşen tabanlı model

Bu desen miras yerine kompozisyon kullanır ve Birliğin geri kalanının çalışma şekline daha yakındır. Oyununuzu nasıl yapılandırdığınıza bağlı olarak, bunun için Unity bileşen sistemini kullanmak mümkün / yararlı olabilir ... ya da değil, kilometreniz değişebilir.

Sınıfın Her nesne Itemgibi bileşenlerin listesini olurdu Equipable, Edible, Sellable, Drinkable, vb bir öğe bir veya her bileşenin hiçbiri sahip olabilir (örneğin, çikolatadan yapılmış bir kask hem olurdu Equipableve Edibleve bir arsa kritik olmadığı zaman görev öğesi de Sellable). Bileşene özgü programlama mantığı, bu bileşene uygulanır. Kullanıcı bir öğeye sağ tıkladığında, öğenin bileşenleri yinelenir ve var olan her bileşen için bağlam menüsü girişleri eklenir. Kullanıcı bu girdilerden birini seçtiğinde, bu girdiyi ekleyen bileşen seçeneği işler.

Her bileşen için bir alt düğüme sahip olarak XML dosyanızda bunu temsil edebilirsiniz. Misal:

   <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>

Değerli açıklamalarınız ve soruma cevap vermek için ayırdığınız zaman için teşekkür ederim. Hangi yöntemle gideceğime henüz karar vermemiş olsam da, sağladığınız alternatif uygulama yöntemlerini takdir ediyorum. Oturup benim için hangi yöntemin daha iyi çalışacağını düşüneceğim ve oradan gideceğim. Teşekkürler :)
Mike Hunt

@MikeHunt Bileşen listesi modeli kesinlikle araştırmanız gereken bir şeydir, çünkü bir dosyadan öğe tanımlarının yüklenmesi ile iyi çalışır.
user253751 18:15

@immibis ilk denemem buna benzer olduğu için ilk olarak deneyeceğim şey bu. Teşekkürler :)
Mike Hunt

Eski yanıt, ancak bir "bileşen listesi" modelinin nasıl uygulanacağına dair herhangi bir belge var mı?
Jeff

@Jeff Bu kalıbı oyununuza uygulamak ve nasıl yapılacağına dair sorularınız varsa, lütfen yeni bir soru gönderin.
Philipp

9

Mike Hunt, sorunuz beni çok ilgilendirdi, tam bir çözüm uygulamaya karar verdim. Üç saat farklı şeyler denedikten sonra, bu adım adım çözümü buldum:

(Bu çok iyi bir kod DEĞİLDİR, bu yüzden herhangi bir düzenlemeyi kabul edeceğimizi lütfen unutmayın)

İçerik Paneli Oluşturma

(Bu panel, içerik menüsü düğmelerimiz için bir kap olacaktır)

  • Yeni oluşturmak UI Panel
  • anchorSol alta ayarla
  • width300 olarak ayarlayın (istediğiniz gibi)
  • Panele yeni bir bileşen ekleyin Vertical Layout Groupve Child Alignmentüst merkeze, Child Force Expandgenişliğe (yükseklik değil) ayarlayın
  • Panele yeni bir bileşen ekleyin Content Size Fitterve Vertical FitMin Size olarak ayarlayın
  • Prefabrik olarak kaydet

(Bu noktada Panelimiz bir satıra daralır. Normaldir. Bu panel düğmeleri çocuk olarak kabul eder, dikey olarak hizalar ve özet içerik yüksekliğine kadar uzanır)

Örnek Düğmesi Oluşturma

(Bu düğme, içerik menüsü öğelerini göstermek için örneklenecek ve özelleştirilecektir)

  • Yeni kullanıcı arayüzü oluştur düğmesi
  • Ayarlamak anchorSol üste
  • Düğmeye 30'a Layout Elementayarlanmış yeni bir bileşen ekleyin Min Height,Preferred Height ekleme
  • Prefabrik olarak kaydet

ContextMenu.cs betiği oluşturma

(Bu komut dosyasının İçerik Menüsünü oluşturan ve gösteren bir yöntemi vardır)

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);
        }
    }
}
  • Bu komut dosyasını bir Kanvas'a ekleyin ve alanları doldurun. Sürükle-bırak ContentPanelyuvası tekabül eden prefabrik ve yuvaya sürükleme Tuval kendisi Canvas.

ItemController.cs betiği oluşturma

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);
    }
}
  • Sahnede örnek nesne oluşturun (yani Cube), kamera tarafından görülebilecek şekilde yerleştirin ve bu komut dosyasını ona ekleyin. Sürükle-bırak sampleButtonyöntemini ilgili yuvaya sürükleyin .

Şimdi çalıştırmayı deneyin. Nesneyi sağ tıklattığınızda, yaptığımız listeyle doldurulmuş içerik menüsü görünmelidir. Düğmelere basıldığında konsolda bazı metinler yazdırılır ve içerik menüsü yok edilir.

Olası iyileştirmeler:

  • daha da genel!
  • daha iyi bellek yönetimi (kirli bağlantılar, paneli yok etmeme, devre dışı bırakma)
  • bazı süslü şeyler

Örnek proje (Unity Personal 5.2.0, VisualStudio Eklentisi): https://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp=sharing


Vay canına bunu uygulamak için zaman ayırdığınız için çok teşekkür ederim. Bilgisayarıma geri döndüğümde uygulamanızı test edeceğim. Bence açıklama amacıyla, kullanılabilecek yöntemler için çeşitli açıklamalara dayanarak Philipp'in cevabını kabul edeceğim. Cevabınızı burada bırakacağım çünkü bunun son derece değerli olduğuna ve gelecekte bu soruyu görüntüleyenlerin gerçek bir uygulamaya ve bu tür şeyleri bir oyunda uygulamak için bazı yöntemlere sahip olacağına inanıyorum. Çok teşekkür ederim ve aferin. Ben de buna oy verdim :)
Mike Hunt

1
Rica ederim. Bu cevabın birine yardım etmesi harika olurdu.
Egzersiz
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.