Làm thế nào để thiết kế menu ngữ cảnh dựa trên bất cứ đối tượng nào?


21

Tôi đang tìm giải pháp cho hành vi "Tùy chọn nhấp chuột phải".

Về cơ bản bất kỳ và mọi vật phẩm trong trò chơi, khi nhấp chuột phải, có thể hiển thị một tập hợp các tùy chọn dựa trên bất kỳ đối tượng nào.

Ví dụ nhấp chuột phải cho các kịch bản khác nhau :

Hàng tồn kho: Mũ bảo hiểm hiển thị các tùy chọn (Trang bị, Sử dụng, Thả, Mô tả)

Ngân hàng: Mũ bảo hiểm hiển thị các tùy chọn (Lấy 1, Lấy X, Lấy tất cả, Mô tả)

Tầng: Mũ bảo hiểm hiển thị các tùy chọn (Đưa, Đi bộ tại đây, Mô tả)

Rõ ràng mỗi tùy chọn bằng cách nào đó chỉ ra một phương pháp nhất định thực hiện những gì được nói. Đây là một phần của vấn đề tôi đang cố gắng tìm hiểu. Với rất nhiều tùy chọn hiệu lực cho một mục duy nhất, làm thế nào để các lớp của tôi được thiết kế theo cách sao cho không quá lộn xộn?

  • Tôi đã nghĩ về thừa kế nhưng điều đó có thể thực sự dài dòng và chuỗi có thể rất lớn.
  • Tôi đã nghĩ về việc sử dụng các giao diện, nhưng điều này có lẽ sẽ hạn chế tôi một chút vì tôi sẽ không thể tải dữ liệu mục từ tệp Xml và đặt nó vào một lớp "Mục" chung.

Tôi đang dựa trên kết quả mong muốn của mình cho một trò chơi tên là Runescape. Mọi đối tượng có thể được nhấp chuột phải trong trò chơi và tùy thuộc vào vị trí của nó và vị trí của nó (kho, sàn, ngân hàng, v.v.) hiển thị một bộ tùy chọn khác nhau có sẵn để người chơi tương tác.

Làm thế nào tôi sẽ đi về việc đạt được điều này? Trước tiên, tôi nên thực hiện phương pháp nào, quyết định lựa chọn nào NÊN được hiển thị và sau khi nhấp, làm thế nào để gọi phương thức tương ứng.

Tôi đang sử dụng C # và Unity3D, nhưng bất kỳ ví dụ nào được cung cấp không phải liên quan đến một trong số chúng vì tôi theo mô hình trái ngược với mã thực tế.

Bất kỳ trợ giúp đều được đánh giá cao và nếu tôi không rõ ràng trong câu hỏi hoặc kết quả mong muốn của mình, xin vui lòng gửi bình luận và tôi sẽ có xu hướng càng sớm càng tốt.

Đây là những gì tôi đã cố gắng cho đến nay:

  • Tôi thực sự đã quản lý để thực hiện một lớp "Vật phẩm" chung chứa tất cả các giá trị cho các loại vật phẩm khác nhau (tấn công thêm, phòng thủ thêm, chi phí, v.v.). Các biến này được điền bởi dữ liệu từ tệp Xml.
  • Tôi đã nghĩ về việc đặt mọi phương thức tương tác có thể có bên trong lớp Item nhưng tôi nghĩ rằng đây là hình thức lộn xộn và nghèo nàn không thể tin được. Có lẽ tôi đã sử dụng sai cách tiếp cận để triển khai loại hệ thống này khi chỉ sử dụng một lớp và không phân lớp cho các mục khác nhau, nhưng đó là cách duy nhất để tôi có thể tải dữ liệu từ Xml và lưu trữ trong lớp.
  • Lý do tôi chọn tải tất cả các mục của mình từ tệp Xml là do trò chơi này có khả năng cho hơn 40.000 mục. Nếu toán của tôi đúng, một lớp cho mỗi mục là rất nhiều lớp.

Nhìn vào danh sách các lệnh của bạn, ngoại trừ "Trang bị", có vẻ như tất cả chúng đều chung chung và áp dụng bất kể mục đó là gì - lấy, thả, mô tả, di chuyển ở đây, v.v.
tro999

Nếu một vật phẩm không thể giao dịch, thay vì "Thả", nó có thể có "Phá hủy"
Mike Hunt

Thành thật mà nói, nhiều trò chơi giải quyết điều này bằng cách sử dụng DSL - một ngôn ngữ kịch bản tùy chỉnh cụ thể cho trò chơi.
corsiKa

1
+1 để lập mô hình trò chơi của bạn sau RuneScape. Tôi thích trò chơi đó.
Zenadix

Câu trả lời:


23

Như với mọi thứ trong phát triển phần mềm, không có giải pháp lý tưởng. Chỉ có giải pháp lý tưởng cho bạn và dự án của bạn. Đây là một số bạn có thể sử dụng.

Cách 1: Mô hình thủ tục

Phương pháp cổ xưa lỗi thời .

Tất cả các mục đều là các kiểu dữ liệu cũ đơn giản mà không có bất kỳ phương thức nào nhưng rất nhiều thuộc tính công khai đại diện cho tất cả các thuộc tính mà một mục có thể có, bao gồm một số cờ boolean như isEdible, isEquipablev.v. xác định các mục trình đơn ngữ cảnh có sẵn cho nó (có thể bạn cũng có thể làm mà không có các cờ này khi bạn có thể lấy nó từ các giá trị của các thuộc tính khác). Có một số phương thức như Eat, Equipv.v. trong lớp trình phát của bạn, trong đó có một mục và có tất cả logic để xử lý nó theo các giá trị thuộc tính.

Tùy chọn 2: Mô hình hướng đối tượng

Đây là một giải pháp OOP-by-the-book dựa trên sự kế thừa và đa hình.

Có một lớp cơ sở Itemmà từ đó các mục khác như EdibleItem, EquipableItemvv kế thừa. Lớp cơ sở nên có một phương thức công khai GetContextMenuEntriesForBank, GetContextMenuEntriesForFloorv.v ... trả về một danh sách ContextMenuEntry. Mỗi lớp kế thừa sẽ ghi đè các phương thức này để trả về các mục menu ngữ cảnh phù hợp với loại mục này. Nó cũng có thể gọi cùng một phương thức của lớp cơ sở để có được một số mục mặc định có thể áp dụng cho bất kỳ loại mục nào. Đây ContextMenuEntrysẽ là một lớp với một phương thức Performmà sau đó gọi phương thức có liên quan từ Mục đã tạo ra nó (bạn có thể sử dụng một đại biểu cho việc này).

Về các vấn đề của bạn khi triển khai mẫu này khi đọc dữ liệu từ tệp XML: Trước tiên, hãy kiểm tra nút XML cho từng mục để xác định loại mục, sau đó sử dụng mã chuyên biệt cho từng loại để tạo một thể hiện của lớp con phù hợp.

Tùy chọn 3: Mô hình dựa trên thành phần

Mẫu này sử dụng thành phần thay vì kế thừa và gần hơn với cách phần còn lại của Unity hoạt động. Tùy thuộc vào cách bạn cấu trúc trò chơi của mình, có thể có thể / có lợi khi sử dụng hệ thống thành phần Unity cho việc này ... hoặc không, số dặm của bạn có thể thay đổi.

Mỗi đối tượng của lớp Itemsẽ có một danh sách các thành phần như Equipable, Edible, Sellable, Drinkable, vv Một mặt hàng có thể có một hoặc không có của mỗi thành phần (ví dụ, một mũ bảo hiểm làm bằng sô cô la sẽ được cả hai EquipableEdible, và khi nó không phải là một âm mưu quan trọng mục nhiệm vụ cũng Sellable). Logic lập trình dành riêng cho thành phần được triển khai trong thành phần đó. Khi người dùng nhấp chuột phải vào một mục, các thành phần của mục được lặp lại và các mục menu ngữ cảnh được thêm vào cho mỗi thành phần tồn tại. Khi người dùng chọn một trong các mục này, thành phần đã thêm mục đó xử lý tùy chọn.

Bạn có thể biểu thị điều này trong tệp XML của mình bằng cách có một nút phụ cho mỗi thành phần. Thí dụ:

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

Cảm ơn bạn đã giải thích có giá trị của bạn và thời gian bạn đã trả lời câu hỏi của tôi. Mặc dù tôi chưa quyết định phương pháp nào tôi sẽ thực hiện, tôi đánh giá cao các phương pháp triển khai thay thế mà bạn đã cung cấp. Tôi sẽ ngồi xuống và suy nghĩ về phương pháp nào sẽ làm việc tốt hơn cho tôi và đi từ đó. Cảm ơn :)
Mike Hunt

@MikeHunt Mô hình danh sách các thành phần chắc chắn là thứ bạn nên điều tra, vì nó hoạt động độc đáo với việc tải các định nghĩa mục từ một tệp.
dùng253751

@immibis đó là những gì tôi sẽ thử đầu tiên vì nỗ lực ban đầu của tôi tương tự như vậy. Cảm ơn :)
Mike Hunt

Câu trả lời cũ, nhưng có tài liệu nào về cách triển khai mô hình "danh sách các thành phần" không?
Jeff

@Jeff Nếu bạn muốn triển khai mô hình này trong trò chơi của mình và có bất kỳ câu hỏi nào về cách thức, vui lòng gửi câu hỏi mới.
Phi

9

Vì vậy, Mike Hunt, câu hỏi của bạn rất quan tâm đến tôi, tôi quyết định thực hiện một giải pháp đầy đủ. Sau ba giờ thử những thứ khác nhau, tôi đã kết thúc với giải pháp từng bước này:

(Xin lưu ý rằng đây không phải là mã rất tốt, vì vậy tôi sẽ chấp nhận mọi chỉnh sửa)

Tạo bảng nội dung

(Bảng này sẽ là nơi chứa các nút menu ngữ cảnh của chúng tôi)

  • Tạo mới UI Panel
  • Đặt anchorở dưới cùng bên trái
  • Đặt widththành 300 (như bạn muốn)
  • Thêm vào bảng điều khiển một thành phần mới Vertical Layout Groupvà đặt thành Child Alignmenttrung tâm phía trên, Child Force Expandtheo chiều rộng (không phải chiều cao)
  • Thêm vào bảng điều khiển một thành phần mới Content Size Fittervà đặt thành Vertical FitKích thước tối thiểu
  • Lưu nó dưới dạng prefab

(Tại thời điểm này, Bảng điều khiển của chúng tôi sẽ thu nhỏ lại thành một dòng. Điều đó là bình thường. Bảng này sẽ chấp nhận các nút khi còn nhỏ, căn chỉnh chúng theo chiều dọc và kéo dài theo chiều cao nội dung tóm tắt)

Tạo nút mẫu

(Nút này sẽ được khởi tạo và tùy chỉnh để hiển thị các mục menu ngữ cảnh)

  • Tạo nút UI mới
  • Đặt anchorở trên cùng bên trái
  • Thêm vào nút một thành phần mới Layout Element, thiết lậpMin Height thành 30, Preferred Heightthành 30
  • Lưu nó dưới dạng prefab

Tạo tập lệnh ContextMothy.cs

(Tập lệnh này có phương thức tạo và hiển thị Menu ngữ cảnh)

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);
        }
    }
}
  • Đính kèm tập lệnh này vào Canvas và điền vào các trường. Kéo-n-drop ContentPanelprefab vào vị trí tương ứng và kéo Canvas vào vị trí đó Canvas.

Tạo tập lệnh ItemControll.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);
    }
}
  • Tạo đối tượng mẫu trong cảnh (nghĩa là Cube), đặt nó hiển thị cho camera và đính kèm tập lệnh này vào nó. Kéo-n-drop sampleButtonprefab vào khe tương ứng.

Bây giờ, hãy thử chạy nó. Khi bạn nhấp chuột phải vào đối tượng, menu ngữ cảnh sẽ xuất hiện, được điền với danh sách mà chúng tôi đã tạo. Nhấn các nút sẽ in vào bàn điều khiển một số văn bản và menu ngữ cảnh sẽ bị hủy.

Những cải tiến có thể có:

  • thậm chí còn chung chung hơn!
  • quản lý bộ nhớ tốt hơn (liên kết bẩn, không phá hủy bảng điều khiển, vô hiệu hóa)
  • một số thứ ưa thích

Dự án mẫu (Unity Personal 5.2.0, Plugin VisualStudio): https://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp=shared


Wow cảm ơn bạn rất nhiều vì đã dành thời gian trong ngày của bạn để thực hiện điều này. Tôi sẽ kiểm tra việc triển khai của bạn ngay khi tôi quay lại máy tính của mình. Tôi nghĩ vì mục đích giải thích, tôi sẽ chấp nhận câu trả lời của Philipp dựa trên nhiều cách giải thích cho các phương pháp có thể được sử dụng. Tôi sẽ để lại câu trả lời của bạn ở đây vì tôi tin rằng nó cực kỳ có giá trị và mọi người xem câu hỏi này trong tương lai sẽ có một triển khai thực tế cũng như một số phương pháp để thực hiện loại điều này trong một trò chơi. Cảm ơn bạn rất nhiều và làm tốt. Tôi cũng đã bỏ phiếu này :)
Mike Hunt

1
Không có gì. Sẽ thật tuyệt nếu câu trả lời này sẽ giúp được ai đó.
Cuộc hành trình
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.