วิธีการออกแบบเมนูบริบทตามวัตถุใด ๆ


21

ฉันกำลังมองหาวิธีแก้ปัญหาสำหรับพฤติกรรม "คลิกขวา"

โดยพื้นฐานทุกรายการในเกมเมื่อคลิกขวาสามารถแสดงชุดของตัวเลือกตามสิ่งที่วัตถุเป็น

ตัวอย่างคลิกขวาสำหรับสถานการณ์ต่าง ๆ :

สินค้าคงคลัง:หมวกกันน็อกแสดงตัวเลือก (สวมใส่ใช้วางคำอธิบาย)

ธนาคาร: Helmet แสดงตัวเลือก (รับ 1, Take X, Take All, Description)

ชั้น:หมวกกันน็อกแสดงตัวเลือกต่าง ๆ (ถ่ายเดินที่นี่รายละเอียด)

เห็นได้ชัดว่าตัวเลือกแต่ละตัวชี้ไปที่วิธีการบางอย่างที่ทำในสิ่งที่พูด นี่เป็นส่วนหนึ่งของปัญหาที่ฉันพยายามหา ด้วยตัวเลือกการมีเสน่ห์มากมายสำหรับรายการเดียวฉันจะให้ชั้นเรียนของฉันออกแบบในลักษณะที่จะไม่ยุ่งมากได้อย่างไร

  • ฉันเคยคิดเกี่ยวกับการสืบทอด แต่นั่นอาจจะยืดเยื้อและห่วงโซ่อาจมีขนาดใหญ่มาก
  • ฉันคิดเกี่ยวกับการใช้อินเทอร์เฟซ แต่สิ่งนี้อาจ จำกัด ฉันเล็กน้อยเพราะฉันจะไม่สามารถโหลดข้อมูลรายการจากไฟล์ Xml และวางลงในคลาส "รายการ" ทั่วไป

ฉันอ้างอิงผลลัพธ์ที่ต้องการในเกมที่ชื่อว่า Runescape วัตถุทุกชิ้นสามารถคลิกขวาในเกมและขึ้นอยู่กับว่ามันอยู่ที่ไหนและอยู่ที่ไหน (สินค้าคงคลัง, พื้น, ธนาคาร ฯลฯ ) แสดงชุดตัวเลือกต่าง ๆ ที่ผู้เล่นสามารถโต้ตอบได้

ฉันจะประสบความสำเร็จในเรื่องนี้ได้อย่างไร ฉันควรใช้วิธีใดก่อนอื่นตัดสินใจว่าควรแสดงตัวเลือกใดและเมื่อคลิกวิธีเรียกวิธีที่สอดคล้องกัน

ฉันกำลังใช้ C # และ Unity3D แต่ตัวอย่างใด ๆ ที่ให้ไว้ไม่จำเป็นต้องเกี่ยวข้องกับทั้งสองอย่างเนื่องจากฉันอยู่หลังรูปแบบเมื่อเทียบกับรหัสจริง

ความช่วยเหลือใด ๆ ที่ชื่นชมมากและถ้าฉันยังไม่ชัดเจนในคำถามของฉันหรือผลลัพธ์ที่ต้องการโปรดโพสต์ความคิดเห็นและฉันจะมีแนวโน้มที่จะมันโดยเร็ว

นี่คือสิ่งที่ฉันได้ลองไปแล้ว:

  • ฉันได้ทำการสร้างคลาส "ไอเท็ม" ทั่วไปที่เก็บค่าทั้งหมดสำหรับไอเท็มประเภทต่าง ๆ (การโจมตีพิเศษ, การป้องกันพิเศษ, ราคา ฯลฯ ... ) ตัวแปรเหล่านี้รับข้อมูลจากไฟล์ Xml
  • ฉันคิดว่าจะวางวิธีการโต้ตอบที่เป็นไปได้ทั้งหมดไว้ในระดับรายการ แต่ฉันคิดว่านี่เป็นรูปแบบยุ่งและไม่น่าเชื่ออย่างไม่น่าเชื่อ ฉันอาจใช้วิธีการที่ผิดในการใช้ระบบประเภทนี้โดยใช้เพียงคลาสเดียวและไม่ได้จัดประเภทย่อยเป็นรายการต่าง ๆ แต่เป็นวิธีเดียวที่ฉันสามารถโหลดข้อมูลจาก Xml และเก็บไว้ในชั้นเรียนได้
  • เหตุผลที่ฉันเลือกโหลดรายการทั้งหมดของฉันจากไฟล์ Xml นั้นเป็นเพราะเกมนี้มีความเป็นไปได้มากกว่า 40,000 รายการ ถ้าคณิตศาสตร์ของฉันถูกต้องคลาสสำหรับแต่ละรายการนั้นมีจำนวนมาก

ดูรายการคำสั่งของคุณยกเว้น "Equip" ดูเหมือนว่าทั้งหมดเป็นคำสั่งทั่วไปและนำไปใช้โดยไม่คำนึงว่ารายการนั้นคืออะไร - เอาวางคำอธิบายย้ายที่นี่ ฯลฯ
ashes999

หากรายการไม่สามารถซื้อขายได้แทนที่จะ "วาง" อาจมี "ทำลาย"
Mike Hunt

เพื่อให้ตรงไปตรงมาอย่างสมบูรณ์เกมหลายเกมแก้ปัญหานี้โดยใช้ DSL - ภาษาสคริปต์ที่กำหนดเองโดยเฉพาะสำหรับเกม
corsiKa

1
+1 สำหรับการสร้างเกมของคุณหลังจาก RuneScape ฉันรักเกมนั้น
Zenadix

คำตอบ:


23

เช่นเดียวกับทุกสิ่งในการพัฒนาซอฟต์แวร์ไม่มีทางออกที่ดี โซลูชันที่เหมาะสำหรับคุณและโครงการของคุณเท่านั้น นี่คือบางส่วนที่คุณสามารถใช้ได้

ตัวเลือก 1: รูปแบบขั้นตอน

วิธีโบราณของ โรงเรียนเก่าที่ล้าสมัย

รายการทั้งหมดเป็นใบ้ประเภทธรรมดาเก่าข้อมูลโดยไม่ต้องวิธีการใด ๆ แต่จำนวนมากคุณลักษณะที่สาธารณะซึ่งเป็นตัวแทนของคุณสมบัติทุกรายการที่อาจมีรวมทั้งธงบูลบางอย่างเช่นisEdible, isEquipableฯลฯ ซึ่งกำหนดสิ่งที่รายการเมนูบริบทที่มีอยู่ให้มัน (บางทีคุณอาจจะยัง ทำโดยไม่ต้องตั้งค่าสถานะเหล่านี้เมื่อคุณสามารถได้มาจากค่าของคุณสมบัติอื่น ๆ ) มีวิธีการบางอย่างเช่นEat, Equipฯลฯ ในชั้นเรียนของผู้เล่นของคุณซึ่งจะมีรายการและซึ่งมีทั้งหมดตรรกะในการดำเนินการดังกล่าวเป็นไปตามค่าแอตทริบิวต์

ตัวเลือก 2: โมเดลเชิงวัตถุ

นี่เป็นวิธีการแก้ปัญหาแบบ OOP-the-book ซึ่งอิงจากการสืบทอดและความหลากหลาย

มีฐานชั้นItemที่รายการอื่น ๆ ที่ชอบEdibleItem, EquipableItemฯลฯ สืบทอด ชั้นฐานควรมีวิธีการที่สาธารณะGetContextMenuEntriesForBank, GetContextMenuEntriesForFloorฯลฯ ContextMenuEntryซึ่งกลับรายการของ แต่ละคลาสที่รับช่วงจะแทนที่เมธอดเหล่านี้เพื่อส่งคืนรายการเมนูบริบทที่เหมาะสมสำหรับประเภทรายการนี้ นอกจากนี้ยังสามารถเรียกวิธีการเดียวกันของคลาสฐานเพื่อรับรายการเริ่มต้นบางรายการที่สามารถใช้ได้กับประเภทรายการใด ๆ ContextMenuEntryจะเป็นชั้นด้วยวิธีการหนึ่งPerformซึ่งเรียกวิธีการที่เกี่ยวข้องจากรายการที่สร้างมันขึ้นมา (คุณสามารถใช้ผู้รับมอบสิทธิ์สำหรับการนี้)

เกี่ยวกับปัญหาของคุณในการนำรูปแบบนี้ไปใช้เมื่ออ่านข้อมูลจากไฟล์ XML: ก่อนอื่นให้ตรวจสอบโหนด XML สำหรับแต่ละรายการเพื่อกำหนดประเภทของรายการจากนั้นใช้รหัสพิเศษสำหรับแต่ละประเภทเพื่อสร้างอินสแตนซ์ของคลาสย่อยที่เหมาะสม

ตัวเลือก 3: โมเดลที่อิงส่วนประกอบ

รูปแบบนี้ใช้องค์ประกอบแทนการสืบทอดและใกล้เคียงกับการทำงานของ Unity ที่เหลือ ขึ้นอยู่กับว่าคุณจัดโครงสร้างเกมอย่างไรอาจเป็นไปได้ / เป็นประโยชน์ในการใช้ระบบคอมโพเนนต์ Unity สำหรับสิ่งนี้ ... หรือไม่ระยะของคุณอาจแตกต่างกันไป

วัตถุของแต่ละชั้นItemจะมีรายชื่อของส่วนประกอบเช่นEquipable, Edible, Sellable, Drinkableและอื่น ๆ รายการสามารถมีหรือไม่มีของแต่ละองค์ประกอบ (เช่นหมวกกันน็อกที่ทำจากช็อคโกแลตจะเป็นทั้งสองEquipableและEdibleและเมื่อมันไม่ได้เป็นพล็อตที่มีความสำคัญ รายการเควสยังSellable) ตรรกะการเขียนโปรแกรมซึ่งใช้เฉพาะกับส่วนประกอบนั้นถูกนำไปใช้ในองค์ประกอบนั้น เมื่อผู้ใช้คลิกขวาที่รายการส่วนประกอบของรายการนั้นจะถูกทำซ้ำและเพิ่มรายการเมนูบริบทสำหรับแต่ละองค์ประกอบที่มีอยู่ เมื่อผู้ใช้เลือกหนึ่งในรายการเหล่านี้ส่วนประกอบที่เพิ่มรายการนั้นจะประมวลผลตัวเลือก

คุณสามารถแสดงสิ่งนี้ในไฟล์ XML ของคุณโดยมี sub-node สำหรับแต่ละองค์ประกอบ ตัวอย่าง:

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

ขอบคุณสำหรับคำอธิบายที่มีค่าและเวลาที่คุณตอบคำถามของฉัน ในขณะที่ฉันยังไม่ได้ตัดสินใจว่าจะใช้วิธีใดฉันขอขอบคุณวิธีการใช้งานอื่นที่คุณได้เตรียมไว้ ฉันจะนั่งลงและคิดว่าวิธีใดจะใช้ได้ผลดีกว่าสำหรับฉันและไปจากที่นั่น ขอบคุณ :)
Mike Hunt

@MikeHunt โมเดล list-of-components เป็นสิ่งที่คุณควรตรวจสอบเพราะมันทำงานได้ดีมากกับการโหลดคำจำกัดความของรายการจากไฟล์
253751

@ กลไกที่เป็นสิ่งที่ฉันจะลองก่อนเป็นความพยายามเริ่มต้นของฉันคล้ายกับที่ ขอบคุณ :)
Mike Hunt

คำตอบเก่า แต่มีเอกสารเกี่ยวกับวิธีการใช้โมเดล "list-of-components" หรือไม่?
Jeff

@Jeff หากคุณต้องการที่จะใช้รูปแบบนี้ในเกมของคุณและมีคำถามใด ๆ เกี่ยวกับวิธีการกรุณาโพสต์คำถามใหม่
ฟิลิป

9

ดังนั้นไมค์ฮันท์คำถามของคุณจึงสนใจฉันฉันตัดสินใจที่จะใช้โซลูชั่นเต็มรูปแบบ หลังจากลองสิ่งที่แตกต่างกันสามชั่วโมงฉันลงเอยด้วยวิธีแก้ปัญหาทีละขั้นตอนนี้:

(โปรดทราบว่านี่ไม่ใช่รหัสที่ดีมากดังนั้นฉันจะยอมรับการแก้ไขใด ๆ )

การสร้างแผงเนื้อหา

(พาเนลนี้จะเป็นคอนเทนเนอร์สำหรับปุ่มเมนูบริบทของเรา)

  • สร้างใหม่ UI Panel
  • ตั้งค่าanchorเป็นซ้ายล่าง
  • ชุด widthเป็น 300 (ตามที่คุณต้องการ)
  • เพิ่มองค์ประกอบใหม่Vertical Layout Groupและตั้งค่าไปยังพาเนลChild Alignmentเป็นกึ่งกลางส่วนบนChild Force Expandเป็นความกว้าง (ไม่ใช่ความสูง)
  • เพิ่มองค์ประกอบใหม่ลงในพาเนล Content Size Fitterและตั้งค่าVertical Fitเป็นขนาดต่ำสุด
  • บันทึกเป็นรูปแบบสำเร็จรูป

(ณ จุดนี้พาเนลของเราจะลดขนาดลงมาเป็นบรรทัดมันเป็นเรื่องปกติแผงนี้จะยอมรับปุ่มเป็นเด็ก ๆ จัดแนวพวกมันในแนวตั้งและยืดความสูงของเนื้อหาสรุป)

การสร้างปุ่มตัวอย่าง

(ปุ่มนี้จะถูกสร้างและปรับแต่งเพื่อแสดงรายการเมนูบริบท)

  • สร้างปุ่ม UI ใหม่
  • ตั้งค่าanchorเป็นมุมบนซ้าย
  • เพิ่มปุ่มใหม่เป็นองค์ประกอบใหม่Layout Elementตั้งค่าMin Heightเป็น 30 Preferred Heightถึง 30
  • บันทึกเป็นรูปแบบสำเร็จรูป

การสร้างสคริปต์ ContextMenu.cs

(สคริปต์นี้มีวิธีการสร้างและแสดงเมนูบริบท)

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);
        }
    }
}
  • แนบสคริปต์นี้กับ Canvas และเติมฟิลด์ ลาก n หล่นสำเร็จรูปในช่วงที่สอดคล้องกันและลากตัวเองไปผ้าใบสล็อตContentPanelCanvas

การสร้างสคริปต์ 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);
    }
}
  • สร้างวัตถุตัวอย่างในฉาก (เช่นCube) วางกล้องให้สามารถมองเห็นได้และแนบสคริปต์นี้เข้ากับมัน ลาก -n-drop sampleButtonprefab ไปยังช่องเสียบที่เกี่ยวข้อง

ตอนนี้พยายามเรียกใช้ เมื่อคุณคลิกขวาที่วัตถุเมนูบริบทจะปรากฏขึ้นบรรจุด้วยรายการที่เราทำ ข้อความที่กดปุ่มจะพิมพ์ลงในคอนโซลข้อความบางส่วนและเมนูบริบทจะถูกทำลาย

การปรับปรุงที่เป็นไปได้:

  • ทั่วไปมากยิ่งขึ้น!
  • การจัดการหน่วยความจำที่ดีขึ้น (ลิงค์สกปรกไม่ทำลายแผงปิดการใช้งาน)
  • บางสิ่งที่แฟนซี

ตัวอย่างโครงการ (Unity Personal 5.2.0, ปลั๊กอิน VisualStudio): https://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp=sharing


ว้าวขอบคุณมากที่สละเวลาในการทำสิ่งนี้ ฉันจะทดสอบการนำไปใช้ของคุณทันทีที่ฉันกลับมาที่คอมพิวเตอร์ ฉันคิดว่าเพื่อจุดประสงค์ในการอธิบายฉันจะยอมรับคำตอบของ Philipp ตามคำอธิบายที่หลากหลายสำหรับวิธีการที่สามารถใช้ได้ ฉันจะทิ้งคำตอบไว้ที่นี่เพราะฉันเชื่อว่ามันมีค่ามากและผู้คนที่ดูคำถามนี้ในอนาคตจะมีการนำไปใช้จริงเช่นเดียวกับวิธีการบางอย่างในการนำเกมนี้มาใช้ ขอบคุณมากและทำได้ดีมาก ฉันยังได้รับการโหวตให้คะแนนด้วยนี้ :)
Mike Hunt

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