文章目录

  • 0 前言
  • 1 程序结构
  • 2 Package类
  • 3 PackagePanel类、ItemInList类
  • 4 Item 类 & ItemType枚举类
  • 5 总结


0 前言

背包系统是游戏中常用的简单系统之一,常见情况中,一个背包在游戏程序中有且只有一个实例存在,所以在设计时采用了单例模式(其实在使用中,单例模式在场景重置时无法重置数据,会导致数据冗余,应该进行优化),在设计过程中,使用了之前Web设计模式中学到的MVC设计模式,由实体层,控制层,显示层构成该系统,相对的,显示层由UI框架以及GUI系统构成,UI框架在下篇博文中详述。

1 程序结构

PackageSystem

  • Package : Class (Control Layer)
  • PackagePanel : Class + PackagePanel : UGUI + ItemInMenu : Class (View Layer)
  • Items : Class (Model) + ItemType : Enum (Model)

2 Package类

该类是背包系统的核心逻辑,利用单例模式设计,主要利用了一个字典来存放道具类,并搭配相应的数据操作方法对字典内的数据进行删取查操作,字典设计为由ItemType作为Key,Item作为value,保证每个Item在字典中的唯一性。
该类的构成方法有三个,分别为AddItem方法,RemoveItem方法和GetItem方法,作用分别为添加,删除和查找背包内物品,同时利用了事件系统的方法进行了系统文字提示。

Package.cs

using System;
using System.Collections.Generic;
using UnityEngine;

public class Package
{
    private static Package _instance;

    public static Package Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Package();
            }
            
            return _instance;
        }
    }

    private Dictionary<ItemType, Item> _items = new Dictionary<ItemType, Item>();

    public void AddItem(ItemType itemType, Item item)
    {
        if (Instance._items.ContainsKey(itemType))
        {
            Instance._items[itemType].count += item.count;
        }
        else
        {
            Instance._items.Add(itemType, item);
            EventCenter.Broadcast(EventType.AddItem, _items[itemType]);
            Debug.Log("添加物体成功");
        }
        EventCenter.Broadcast(EventType.ShowText, $"获得了{item.count.ToString()}个{item._name}");
    }

    public void RemoveItem(ItemType itemType)
    {
        if (Instance._items.ContainsKey(itemType))
        {
            Instance._items.Remove(itemType);
        }
        EventCenter.Broadcast(EventType.ShowText, $"丢弃了{itemType.ToString()}物体");
    }

    public Item GetItem(ItemType itemType)
    {
        if (Instance._items.ContainsKey(itemType))
        {
            return Instance._items[itemType];   
        }

        return null;
    }
}

3 PackagePanel类、ItemInList类

该类是基于一个BasePanel类制作(在UI框架的文章中),该类中利用一个List存放了面板中的9个道具容器,在每个容器中添加了一个ItemInList类存放Item,以及判断容器中是否有道具。

ItemInList.cs

using UnityEngine;

public class ItemInMenu : MonoBehaviour
{
    public bool isEmpty = true;
    public Item _item;
}

PackagePanel类由3个主要方法构成,分别为

PackagePanel.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using DG.Tweening;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;

public class PackagePanel : BasePanel
{
    private bool _isPackageOn;

    private static bool _isInitPackage;
    private static int itemNum = 0;
    private List<GameObject> _itemList = new List<GameObject>();
    
    void Awake()
    {
        EventCenter.AddListener<bool>(EventType.IsPackageOn, ClosePackage);
        EventCenter.AddListener<Item>(EventType.AddItem, AddItem);
    }

    private void Update()
    {
        if (_isPackageOn)
        {
            IsMouseHoldOnItem();
        }
    }

    private void Start()
    {
        GameObject[] gameObjects = new GameObject[9];
        for (int i = 0; i < GetComponentsInChildren<ItemInMenu>().Length; i++)
        {
            gameObjects[i] = GetComponentsInChildren<ItemInMenu>()[i].gameObject;
        }
        _itemList = gameObjects.ToList();

        StartCoroutine(RefreshPanel());
    }

    private void AddItem(Item item)
    {
        var onlyOne = 0;
        for (int i = 0; i < _itemList.Count; i++)
        {
            var tempItem = _itemList[i].GetComponent<ItemInMenu>();
            if (tempItem.isEmpty && onlyOne == 0) 
            {
                tempItem.isEmpty = false;
                tempItem._item = item;
                onlyOne++;
            }
        }
    }

    public override void OnEnter()
    {
        if (!_isInitPackage)
        {
            GetComponent<CanvasGroup>().alpha = 0;
            _isInitPackage = true;
            return;
        }
        _isPackageOn = true;
        GetComponent<CanvasGroup>().alpha = 0;
        Tweener twe =  DOTween.To(() => GetComponent<CanvasGroup>().alpha, x => GetComponent<CanvasGroup>().alpha = x,
            1f, 1f);
        twe.OnComplete(() =>
        {
            EventCenter.Broadcast(EventType.IsGamePause);
        });
    }

    public override void OnExit()
    {
        _isPackageOn = false;
        Time.timeScale = 1f;
        Tweener twe = DOTween.To(() => GetComponent<CanvasGroup>().alpha, x => GetComponent<CanvasGroup>().alpha = x,
            0f, 1f);
        twe.OnComplete(() =>
        {
            EventCenter.Broadcast(EventType.IsGameResume);
        });
    }

    private void ClosePackage(bool isPackageOpen)
    {
        if (!isPackageOpen)
        {
            OnExit();
        }
        else
        {
            OnEnter();
        }
    }

    private void IsMouseHoldOnItem()
    {
        GameObject tempObject = GetOverUI(GetComponentInParent<GraphicRaycaster>().gameObject);
        if (tempObject != null && _isPackageOn && tempObject.CompareTag("ItemMenu"))
        {
            ItemInMenu tempInMenu = tempObject.GetComponent<ItemInMenu>();
            if (tempInMenu.isEmpty)
            {
                return;
            }
            
            if (!tempInMenu.isEmpty)
            {
                Item tempItem = tempInMenu._item;
                if (tempItem != null)
                {
                    FindUIElement("ItemName").GetComponent<Text>().text = tempItem._name;
                    FindUIElement("ItemDetail").GetComponent<Text>().text = tempItem.detail;
                }
            }
        }
    }

    IEnumerator RefreshPanel()
    {
        while (true)
        {
            yield return new WaitForSeconds(0.1f);
            for (int i = 0; i < _itemList.Count; i++)
            {
                var tempList = _itemList[i].GetComponent<ItemInMenu>();
                if (!tempList.isEmpty)
                {
                    var item = Package.Instance.GetItem(tempList._item._itemType);
                    _itemList[i].GetComponent<Image>().sprite = Resources.Load<Sprite>(item.image);
                    _itemList[i].GetComponentInChildren<Text>().text = item.count.ToString();
                }
            }
        }
    }
}

4 Item 类 & ItemType枚举类

以笔者在自己的期末课程设计中使用的道具类为例,相应的需要在道具的实体类中设计道具所需的属性以及响应拾取动作的方法,这是一个个性化十分高的类,可以自己设计。

ItemType.cs

public enum ItemType
{
    _9mmAmmo = 1,
    _Key = 2,
    _Paper = 3
}

Item.cs

using System;
using UnityEngine;
using UnityEngine.Serialization;

public class Item: MonoBehaviour
{
    public ItemType _itemType;
    public string _name;
    public int count;
    public string image;
    public string detail;
    public bool isTakeUp;

    protected void OnTriggerEnter(Collider other)
    {
        if (!isTakeUp)
        {
            EventCenter.Broadcast(EventType.ShowText, $"按下“E”键拾取物品(程序员比较菜,容易有bug,不行就多按几下)");
        }
    }

    protected void OnTriggerStay(Collider other)
    {
        OnPlayerStay(other);
    }

    protected virtual void OnPlayerStay(Collider other)
    {
        if (other.gameObject.CompareTag("Player") && Input.GetKey(KeyCode.E) && !isTakeUp)
        {
            Package.Instance.AddItem(_itemType, GetComponent<Item>());
            gameObject.GetComponent<Item>().isTakeUp = true;
            gameObject.GetComponent<MeshRenderer>().enabled = false;
        }
    }
}

5 总结

这个背包系统相对的较为简陋,还需进行优化,在此进行阐述,希望可以为大家给予一些帮助。