Как настроить соответствие между ScriptableObject и Enum
У меня есть ScriptableObject для предметов, которые игрок может собирать. Это может быть монета, палка, камень, кокос и так далее. Такой объект имеет название, спрайт и тип:
public enum FancyItemType
{
Coin, Stick, Stone, Coconut
}
[CreateAssetMenu(fileName = "New Fancy Item", menuName = "New item", order = 51)]
public class FancyItem : ScriptableObject
{
public FancyItemType Type;
public Sprite Sprite;
public string Name;
}
Я создал и настроил такие объекты в юнити. Мне хотелось бы получать такой FancyItem по перечислению Enum. Например, я хочу выдать игроку 10 палок:
void ReceiveBonus (FancyItemType type, int amount) {
// Эта функция принимает ТИП предмета, который игроку надо выдать
// Функция должна каким-то образом узнать спрайт и название предмета по типу
}
...
ReceiveBonus(FancyItemType.Stick, 10); // Передаем тип предмета -- палка, 10 штук
Как я могу получить FancyItem со всеми данными: спрайт и имя, по перечислению Enum?
У меня есть такая идея: сделать класс, в который я в инспекторе брошу ссылки на ScriptableObject, а он будет по Enum выдавать необходимый:
public class FancyItemHelper : MonoBehaviour {
[SerializeField] private FancyItem Coin, Stick, Stone, Coconut;
public FancyItem GetItem (FancyItemType type) {
switch (type) {
case FancyItemType.Coin:
return Coin;
case FancyItemType.Stick:
return Stick;
case FancyItemType.Stone:
return Stone;
case FancyItemType.Coconut:
return Coconut;
}
}
}
Однако мне такой подход слишком сложным. Как мне следует это реализовать?
Ответы (2 шт):
Есть вариант с созданием обьекта на сцене возможно с использованием синглтона для доступности из любой точки (на ваше усмотрение), со скриптом который будет хранить в себе List с нужным вами элементами, заполняемые и изменяемые в испекторе
public class GameItems : MonoBehaviour
{
[SerializeField] private List<FancyItem> _fancyItems;
// Сюда также можно добавить таким же способом любой другой список предметов
public static Instance { get; private set;}
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(this.gameObject);
return;
}
Destroy(this.gameObject);
}
// Вернёт null если не смог найти по типу, можно обработать как угодно
public FancyItem GetFancyItemByType(FancyItemType fancyItemType)
{
foreach (FancyItem fancyItem in _fancyItems)
{
if (fancyItem.Type == fancyItemType)
{
return fancyItem;
}
}
return null;
}
}
Возможно это решение которое Вам нужно!
Есть два стула.
- Использовать словарь
- Не использовать словарь и енамы с ним заодно.
Первый самый легкий - делать то же самое, что в свиче, только со словарем:
using System.Collections.Generic;
using UnityEngine;
namespace ScriptableHell
{
public interface IConverter<in TIn, out TOut>
{
TOut Convert(TIn from);
}
public class EnumToItem : MonoBehaviour, IConverter<FancyItemType, FancyItem>
{
[SerializeField] private FancyItem _coin, _stick, _kamenbYaNeDam, _coconut;
private Dictionary<FancyItemType, FancyItem> _items;
private void Awake()
{
_items = new Dictionary<FancyItemType, FancyItem>()
{
{FancyItemType.Coin, _coin},
{FancyItemType.Coconut, _coconut},
{FancyItemType.Stone, _kamenbYaNeDam},
{FancyItemType.Stick, _stick}
};
}
public FancyItem Convert(FancyItemType from) => _items[from];
}
}
Второй малость посложнее. Отрефакторить код и удалить енамы.
Жахнуть интерфейс:
using UnityEngine;
namespace ScriptableHell
{
public interface IItem
{
Sprite Sprite { get; }
string Name { get; }
}
}
Реализовать его в наследнике SO:
using UnityEngine;
namespace ScriptableHell
{
[CreateAssetMenu(menuName = "New/Item", fileName = "NewItem")]
public class Item : ScriptableObject, IItem
{
[field: SerializeField] public Sprite Sprite { get; private set; }
[field: SerializeField] public string Name { get; private set; }
}
}
Дальше просто через декораторы эти айтемы расширять. Как пример - айтем, который можно поднимать и кидать. Снова интерфейсы:
using UnityEngine;
namespace ScriptableHell
{
public interface IPickable
{
IItem Pick();
}
public interface IThrowable
{
void Throw(Vector3 direction, float distance);
}
}
И их реализация:
using System.Collections;
using UnityEngine;
namespace ScriptableHell
{
[RequireComponent(typeof(SpriteRenderer), typeof(Collider2D))]
public class PickableItem : MonoBehaviour, IItem, IPickable, IThrowable
{
[SerializeField] private Item _item;
private IItem _origin;
private const float ThrowingTime = 3f;
public Sprite Sprite => _origin.Sprite;
public string Name => _origin.Name;
public void Init(IItem origin)
{
if (_origin != null) return;
_origin = origin;
GetComponent<SpriteRenderer>().sprite = _origin.Sprite;
}
public IItem Pick()
{
Destroy(gameObject);
return _origin;
}
public void Throw(Vector3 direction, float distance)
{
StartCoroutine(Throwing(direction, distance));
}
private void OnValidate()
{
if (_item == null)
{
GetComponent<SpriteRenderer>().sprite = null;
return;
}
GetComponent<SpriteRenderer>().sprite = _item.Sprite;
}
private void Awake()
{
if (_item == null) return;
_origin = _item;
}
private IEnumerator Throwing(Vector3 direction, float distance)
{
var startPosition = transform.position;
var targetPosition = startPosition + direction * distance;
var elapsedTime = 0f;
while (elapsedTime < ThrowingTime)
{
var progress = elapsedTime / ThrowingTime;
elapsedTime += Time.deltaTime;
transform.position = Vector3.Lerp(startPosition, targetPosition, progress);
yield return null;
}
}
}
}
PickableItem - декоратор для бедных, т.к. у MonoBehaviour нет нормальных конструкторов и приходится делать это. _item нужен лишь для того, чтобы можно было распихать этот поднимабельный объект по сцене и в инспекторе ему закинуть нужный айтем. Также сундук, который содержит несколько однотипных айтемов: контейнер:
using System;
using UnityEngine;
namespace ScriptableHell
{
[Serializable]
public class ItemContainer : IItem
{
[field: SerializeField] public Item Item { get; private set; }
[field: SerializeField] public int Amount { get; private set; }
public Sprite Sprite => Item.Sprite;
public string Name => Item.Name;
}
}
сундук:
using UnityEngine;
namespace ScriptableHell
{
public class BonusChest : MonoBehaviour, IPickable
{
[SerializeField] private ItemContainer _itemContainer;
public IItem Pick()
{
Destroy(gameObject);
return _itemContainer;
}
}
}
Компонент, который будет все поднимать висит на плеере:
using UnityEngine;
namespace ScriptableHell
{
[RequireComponent(typeof(Collider2D))]
public class PickArea : MonoBehaviour
{
[SerializeField] private Player _player;
private void OnTriggerEnter2D(Collider2D other)
{
if (other.TryGetComponent(out IPickable pickable))
{
Pick(pickable.Pick());
}
}
private void Pick(ItemContainer container)
{
for (var i = 0; i < container.Amount; i++)
{
_player.GiveItem(container.Item);
}
}
private void Pick(IItem item)
{
if (item is ItemContainer container)
{
Pick(container);
return;
}
_player.GiveItem(item);
}
}
}
Сам игрок:
using System.Collections.Generic;
using UnityEngine;
namespace ScriptableHell
{
public class Player : MonoBehaviour
{
[SerializeField] private Transform _parent;
[SerializeField] private ItemUIPresentation _prefab;
[SerializeField] private PickableItem _throwablePrefab;
[SerializeField] private float _speed = 5f;
private readonly List<IItem> _items = new List<IItem>();
private readonly List<ItemUIPresentation> _spawnedItems = new List<ItemUIPresentation>();
public void GiveItem(IItem item)
{
_items.Add(item);
Draw(_items);
}
private void Update()
{
var x = Input.GetAxis("Horizontal");
var y = Input.GetAxis("Vertical");
transform.position += new Vector3(x, y) * Time.deltaTime * _speed;
if (Input.GetKeyDown(KeyCode.Space))
{
Throw();
}
}
private void Throw()
{
if (_items.Count == 0) return;
var item = _items[0];
var pickable = Instantiate(_throwablePrefab, transform.position + transform.up * 2f, Quaternion.identity);
pickable.Init(item);
pickable.Throw(transform.up, 5f);
_items.Remove(item);
Draw(_items);
}
private void Draw(List<IItem> items)
{
Clear();
if (items.Count == 0) return;
foreach (var item in items)
{
var spawned = Instantiate(_prefab, _parent);
spawned.Init(item);
_spawnedItems.Add(spawned);
}
}
private void Clear()
{
if (_spawnedItems.Count == 0) return;
foreach (var item in _spawnedItems)
{
Destroy(item.gameObject);
}
_spawnedItems.Clear();
}
}
}
Игрок у меня еще и инвентарь сам отрисовывает, поэтому ему нужен префаб ячейки инвентаря, которая тоже является декоратором для бедных:
using UnityEngine;
using UnityEngine.UI;
namespace ScriptableHell
{
[RequireComponent(typeof(RectTransform), typeof(Image))]
public class ItemUIPresentation : MonoBehaviour, IItem
{
private IItem _origin;
public Sprite Sprite => _origin.Sprite;
public string Name => _origin.Name;
public void Init(IItem item)
{
_origin = item;
GetComponent<Image>().sprite = _origin.Sprite;
}
}
}
Не нужно никаких синглтонов/словарей/возвращений null и т.д.
Не знаю зачем, но вот так это все в итоге выглядит