객체가 무엇인지에 따라 상황에 맞는 메뉴를 디자인하는 방법은 무엇입니까?


21

"오른쪽 클릭 옵션"동작에 대한 솔루션을 찾고 있습니다.

기본적으로 게임의 모든 항목은 마우스 오른쪽 버튼을 클릭하면 오브젝트가 무엇이든 기반으로 옵션 세트를 표시 할 수 있습니다.

다른 시나리오에 대한 오른쪽 클릭 예 :

인벤토리 : 헬멧 표시 옵션 (장비, 사용, 놓기, 설명)

은행 : 헬멧 옵션 표시 (Take 1, Take X, Take Take, Description)

바닥 : 헬멧에 옵션 표시 (테이크, 여기로 이동, 설명)

분명히 각 옵션은 어떻게 든 말하는 것을 수행하는 특정 방법을 가리 킵니다. 이것은 내가 알아 내려고하는 문제의 일부입니다. 하나의 항목에 대한 많은 포지셔닝 옵션을 사용하면 어떻게 지저분하지 않도록 수업을 디자인 할 수 있습니까?

  • 상속에 대해 생각했지만 실제로는 오래 걸리고 체인이 거대 할 수 있습니다.
  • 인터페이스 사용에 대해 생각했지만 Xml 파일에서 항목 데이터를로드하여 일반 "Item"클래스에 배치 할 수 없으므로 약간 제한 될 수 있습니다.

Runescape라는 게임을 통해 원하는 최종 결과를 얻었습니다. 모든 개체는 게임에서 마우스 오른쪽 단추로 클릭 할 수 있으며 개체의 위치 및 개체 위치 (인벤토리, 플로어, 은행 등)에 따라 플레이어가 상호 작용할 수있는 다양한 옵션이 표시됩니다.

이것을 달성하려면 어떻게해야합니까? 우선 어떤 접근 방식을 취해야하는지, 어떤 옵션을 표시 해야하는지, 일단 클릭하면 해당 메소드를 호출하는 방법을 결정하십시오.

C #과 Unity3D를 사용하고 있지만 실제 코드와 달리 패턴을 따르기 때문에 제공된 예제는 둘 중 하나와 관련이 필요하지 않습니다.

도움을 주셔서 감사합니다. 질문이나 원하는 결과가 명확하지 않은 경우 의견을 게시하면 최대한 빨리 답변 해 드리겠습니다.

내가 지금까지 시도한 것은 다음과 같습니다.

  • 실제로 다른 유형의 항목 (추가 공격, 추가 방어, 비용 등)에 대한 모든 값을 보유하는 일반 "Item"클래스를 구현했습니다. 이 변수는 Xml 파일의 데이터로 채워집니다.
  • 가능한 모든 단일 상호 작용 방법을 Item 클래스 내에 배치하는 것에 대해 생각했지만 이것이 믿을 수 없을 정도로 지저분하고 형편없는 것으로 생각합니다. 아마도 하나의 클래스 만 사용하고 다른 항목에 대한 서브 클래스 화를 사용하지 않고 이러한 종류의 시스템을 구현하기 위해 잘못된 접근 방식을 취했을 것입니다. 그러나 Xml에서 데이터를로드하고 클래스에 저장할 수있는 유일한 방법입니다.
  • Xml 파일에서 모든 항목을로드하기로 선택한 이유는이 게임에서 40,000 개 이상의 항목을 사용할 수 있기 때문입니다. 내 수학이 정확하면 각 항목의 클래스는 많은 클래스입니다.

"Equip"을 제외한 명령 목록을 살펴보면 모든 명령이 일반적이며 항목이 무엇인지에 관계없이 적용, 가져 오기, 삭제, 설명, 여기로 이동하는 등의 적용으로
보입니다

아이템이 "드롭"대신 거래 할 수 없다면 "파괴"를 할 수 있습니다
Mike Hunt

솔직히 말해서, 많은 게임은 게임 고유의 사용자 지정 스크립팅 언어 인 DSL을 사용하여이 문제를 해결합니다.
corsiKa

1
RuneScape 이후 게임 모델링에 +1. 나는 그 게임을 좋아한다.
Zenadix

답변:


23

소프트웨어 개발의 모든 것과 마찬가지로 이상적인 솔루션은 없습니다. 귀하와 귀하의 프로젝트에 이상적인 솔루션 만. 다음은 사용할 수있는 것입니다.

옵션 1 : 절차 모델

고대의 오래된 구식 방법.

모든 항목은 벙어리 일반 오래된 데이터 어떤 방법이없는 유형하지만 같은 일부 부울 플래그를 포함하는 항목이있을 수있는 모든 속성을 대표하는 공공 속성이 많이 있습니다 isEdible, isEquipable상황에 맞는 메뉴 항목이 어쩌면 당신은 또한 수 (그것을 위해 사용할 수있는 결정 등 다른 속성의 값에서 파생 될 수 있으면이 플래그없이 수행하십시오. 같은 몇 가지 방법을 가지고 Eat, Equip항목을 소요하고 모든 논리를 가지고있는 속성 값에 따라이를 처리하는 플레이어 클래스 등.

옵션 2 : 객체 지향 모델

이것은 상속과 다형성을 기반으로 한 OOP-by-the-book 솔루션입니다.

기본 수준이 Item다른 항목이 좋아하는부터 EdibleItem, EquipableItem등 상속을. 기본 클래스는 공개 방법이 있어야 GetContextMenuEntriesForBank, GetContextMenuEntriesForFloor목록을 반환 등 ContextMenuEntry. 각 상속 클래스는이 메소드 유형을 재정 의하여이 항목 유형에 적합한 컨텍스트 메뉴 항목을 리턴합니다. 또한 기본 클래스와 동일한 메소드를 호출하여 모든 항목 유형에 적용 가능한 기본 항목을 얻을 수 있습니다. 이것은 ContextMenuEntry메소드 Perform를 가진 클래스 일 것입니다.이 메소드 는 그것을 생성 한 Item에서 관련 메소드를 호출합니다 ( 대리인 을 사용할 수 있습니다 ).

XML 파일에서 데이터를 읽을 때이 패턴을 구현할 때의 문제점과 관련하여 : 먼저 각 항목의 XML 노드를 검사하여 항목 유형을 판별 한 후 각 유형에 대한 특수 코드를 사용하여 적절한 서브 클래스의 인스턴스를 작성하십시오.

옵션 3 : 구성 요소 기반 모델

이 패턴은 상속 대신 컴포지션을 사용하며 나머지 Unity 작동 방식에 더 가깝습니다. 게임 구성 방법에 따라 Unity 구성 요소 시스템을 사용하는 것이 가능하거나 유리할 수 있습니다 ... 마일리지는 다를 수 있습니다.

클래스의 각 개체는 Item같은 구성 요소 목록을 것이다 Equipable, Edible, Sellable, Drinkable, 등 항목이 하나 또는 각 구성 요소의 없음을 가질 수있다 (예를 들어, 초콜릿으로 만든 헬멧 둘 것 Equipable하고 Edible, 그리고 플롯 중요하지 않을 때 퀘스트 아이템도 Sellable). 컴포넌트에 특정한 프로그래밍 로직은 해당 컴포넌트에서 구현됩니다. 사용자가 항목을 마우스 오른쪽 버튼으로 클릭하면 항목의 구성 요소가 반복되고 존재하는 각 구성 요소에 대한 상황에 맞는 메뉴 항목이 추가됩니다. 사용자가 이러한 항목 중 하나를 선택하면 해당 항목을 추가 한 구성 요소가 옵션을 처리합니다.

각 구성 요소에 대한 하위 노드를 가짐으로써 XML 파일에서이를 나타낼 수 있습니다. 예:

   <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 구성 요소 목록 모델은 파일에서 항목 정의를로드 할 때 잘 작동하므로 반드시 조사해야합니다.
user253751

@immibis는 내 초기 시도가 그것과 비슷하기 때문에 내가 먼저 시도 할 것입니다. 감사합니다 :)
Mike Hunt

오래된 대답이지만 "구성 요소 목록"모델을 구현하는 방법에 대한 문서가 있습니까?
Jeff

@Jeff 게임에서이 패턴을 구현하고 방법에 대한 질문이 있으면 새로운 질문을 게시하십시오.
빌립

9

그래서 Mike Hunt, 귀하의 질문에 관심이 있으니 완전한 솔루션을 구현하기로 결정했습니다. 다른 일을 시도한 세 시간 후에, 나는이 단계별 솔루션으로 끝났습니다.

(이 코드는 좋은 코드가 아니므로 편집을 수락합니다.)

컨텐츠 패널 만들기

(이 패널은 컨텍스트 메뉴 버튼을위한 컨테이너입니다)

  • 새로 만들기 UI Panel
  • anchor왼쪽 아래로 설정
  • width300으로 설정 (원하는대로)
  • 패널에 새로운 구성 요소를 추가 Vertical Layout Group및 세트 Child Alignment, 상부 중앙 Child Force Expand(신장되지 않음) 대 폭
  • 새 구성 요소를 패널에 추가하고 최소 크기로 Content Size Fitter설정Vertical Fit
  • 조립식으로 저장

(이 시점에서 패널은 줄로 줄어 듭니다. 정상입니다.이 패널은 버튼을 자식으로 받아들이고, 수직으로 정렬하고 요약 내용 높이로 확장합니다)

샘플 버튼 만들기

(이 버튼은 상황에 맞는 메뉴 항목을 표시하도록 인스턴스화되고 사용자 정의됩니다)

  • 새로운 UI 버튼 만들기
  • anchor왼쪽 상단으로 설정
  • 버튼에 새 구성 요소 추가 Layout Element설정 Min Height, 30을 Preferred Height30로
  • 조립식으로 저장

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);
        }
    }
}
  • 이 스크립트를 캔버스에 첨부하고 필드를 채우십시오. ContentPanel프리 팹을 해당 슬롯으로 드래그 앤 드롭 하고 캔버스 자체를 슬롯으로 드래그합니다 Canvas.

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하고 카메라에 보이도록 배치하고이 스크립트를 첨부합니다. sampleButton프리 팹을 해당 슬롯으로 드래그 앤 드롭 합니다.

이제 실행 해보십시오. 객체를 마우스 오른쪽 버튼으로 클릭하면 작성한 목록으로 채워진 상황에 맞는 메뉴가 나타납니다. 버튼을 누르면 일부 텍스트가 콘솔에 인쇄되고 상황에 맞는 메뉴가 손상됩니다.

가능한 개선 사항 :

  • 훨씬 더 일반적인!
  • 더 나은 메모리 관리 (더러운 링크, 패널을 파괴하지 않고 비활성화)
  • 멋진 물건

샘플 프로젝트 (Unity Personal 5.2.0, VisualStudio Plugin) : https://drive.google.com/file/d/0B7iGjyVbWvFwUnRQRVVaOGdDc2M/view?usp=sharing


와우 구현에 시간을 내 주셔서 감사합니다. 컴퓨터로 돌아 오자마자 구현을 테스트하겠습니다. 나는 설명의 목적으로, 사용할 수있는 방법에 대한 다양한 설명을 기반으로 필립의 대답을 받아 들일 것이라고 생각합니다. 나는 이것이 매우 가치 있다고 생각하고 앞으로이 질문을 보는 사람들이 실제 구현뿐만 아니라 게임에서 이런 종류의 것을 구현하기위한 몇 가지 방법을 가질 것이기 때문에 귀하의 답변을 여기에 남겨 둘 것입니다. 대단히 감사합니다. 나도 이것을 투표했다 :)
Mike Hunt

1
천만에요. 이 답변이 누군가를 도울 수 있다면 좋을 것입니다.
Exerion
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.