前言
欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第23篇中,我们将探索如何制作一个类似于七日杀和森林的生存游戏。
本篇内容会比较多,我会分几篇来实现,感兴趣的可以关注一下,以免错过内容更新。
本节主要一个简单的库存系统,并完善之前的拾取功能。
库存系统
之前我们做过不少库存系统,这次就实现一个简单的,想要更复杂的可以看看我之前的文章:
【制作100个unity实战之3】从零手戳一个库存背包系统(附项目源码)
【用unity实现100个游戏之13】复刻类泰瑞利亚生存建造游戏——包括建造系统和库存系统(附项目源码)
【制作100个unity实战之6】手戳一个库存系统,非常适合RPG、Roguelike和星露谷物语之类的游戏(附项目源码)
【unity实战】unity3D中的PRG库存系统和换装系统(附项目源码)
素材
https://assetstore.unity.com/packages/2d/gui/icons/gui-parts-159068
绘制简单的库存UI
控制库存开关
新增脚本InventorySystem,控制库存开关
public class InventorySystem : MonoBehaviour { public static InventorySystem Instance { get; set; } // 引入UI界面对象 public GameObject inventoryScreenUl; // 记录物品系统是否打开的状态 [HideInInspector] public bool isOpen; private void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } } void Update() { if (Input.GetKeyDown(KeyCode.Tab)) { // 打开物品系统界面 inventoryScreenUl.SetActive(!isOpen); isOpen = !isOpen; // 设置鼠标锁定模式为无锁定,允许鼠标在界面上移动 Cursor.lockState = isOpen ? CursorLockMode.None : CursorLockMode.Locked; } } }
挂载配置脚本参数
打开库存,我们不希望人物视角还可以移动
修改MouseLook
void FreeLook() { if(InventorySystem.Instance.isOpen) return; //。。。 }
效果
实现物品拖拽功能
新增DragDrop,控制物品拖拽
public class DragDrop : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler { // [SerializeField] private Canvas canvas; // 缓存RectTransform和CanvasGroup组件 private RectTransform rectTransform; private CanvasGroup canvasGroup; // 定义公共静态变量,用于记录当前被拖拽的物体 public static GameObject itemBeingDragged; // 记录物体拖拽前的起始位置和父级对象 private Vector3 startPosition; private Transform startParent; private void Awake() { rectTransform = GetComponent<RectTransform>(); canvasGroup = GetComponent<CanvasGroup>(); } public void OnBeginDrag(PointerEventData eventData) { Debug.Log("OnBeginDrag"); // 在物体开始被拖拽时,降低其不透明度来给用户反馈 canvasGroup.alpha = 0.6f; // 防止射线检测到当前拖拽的物体,以避免与其他UI元素冲突 canvasGroup.blocksRaycasts = false; // 记录物体拖拽前的起始位置和父级对象 startPosition = transform.position; startParent = transform.parent; // 将物体从原有的父级对象移动到画布下,以便它始终在可见范围内 transform.SetParent(transform.root); // 记录当前被拖拽的物体 itemBeingDragged = gameObject; } public void OnDrag(PointerEventData eventData) { // 在物体被拖拽时,使其位置跟随鼠标移动 rectTransform.anchoredPosition += eventData.delta; // 由于Canvas缩放会影响鼠标移动量,因此使用canvas.scaleFactor来保持一致性 // rectTransform.anchoredPosition += eventData.delta / canvas.scaleFactor; } public void OnEndDrag(PointerEventData eventData) { itemBeingDragged = null; // 如果物体被拖拽到了根节点上 if (transform.parent == transform.root) { // 将物体放回起始位置,并将其父对象设置为起始父对象 transform.position = startPosition; transform.SetParent(startParent); } Debug.Log("OnEndDrag"); // 恢复物体的不透明度和射线检测,使其可以再次被拖拽 canvasGroup.alpha = 1f; canvasGroup.blocksRaycasts = true; } }
新增ItemSlot脚本,控制物品插槽
public class ItemSlot : MonoBehaviour, IDropHandler { // 定义公共属性Item,用于获取或设置当前物品槽中的物体 public GameObject Item { get { // 如果物品槽中已经有物体,则返回其子对象,即当前的物体 if (transform.childCount > 0) { return transform.GetChild(0).gameObject; } // 否则返回null return null; } } // 当拖拽物体被放置到当前物品槽时 public void OnDrop(PointerEventData eventData) { Debug.Log("OnDrop"); // 如果当前物品槽中没有物体,则将拖拽的物体设置为当前物品槽的子对象 if (!Item) { DragDrop.itemBeingDragged.transform.SetParent(transform); // 设置拖拽物体的本地坐标为(0,0),即与物品槽重合 DragDrop.itemBeingDragged.transform.localPosition = new Vector2(0, 0); } } }
挂载脚本,并配置参数
物品插槽记得添加CanvasGroup组件
效果
拾取物品
修改InventorySystem
public class InventorySystem : MonoBehaviour { public static InventorySystem Instance { get; set; } // 引入UI界面对象 public GameObject inventoryScreenUl; // 记录物品系统是否打开的状态 [HideInInspector] public bool isOpen; // [HideInInspector] public List<GameObject> slotList = new List<GameObject>(); public List<string> itemList = new List<string>(); private GameObject itemToAdd;//物品插槽 private GameObject whatSlotToEquip;//空闲的槽位 private void Awake() { if (Instance == null) { Instance = this; } else { Destroy(gameObject); } } private void Start() { PopulateSlotList(); } void Update() { if (Input.GetKeyDown(KeyCode.Tab)) { // 打开物品系统界面 inventoryScreenUl.SetActive(!isOpen); isOpen = !isOpen; // 设置鼠标锁定模式为无锁定,允许鼠标在界面上移动 Cursor.lockState = isOpen ? CursorLockMode.None : CursorLockMode.Locked; } } //遍历获取所有的插槽 private void PopulateSlotList() { // 遍历物品系统界面下的所有子物体 foreach (Transform child in inventoryScreenUl.transform.GetChild(0).transform) { // 如果子对象的标签为"Slot" if (child.CompareTag("Slot")) { // 将子对象添加到slotList列表中 slotList.Add(child.gameObject); } } } //添加物品 public void AddToInventory(string itemName) { // 找到下一个空闲的槽位 whatSlotToEquip = FindNextEmptySlot(); // 根据物品名称实例化一个新的物体,并设置其位置和旋转信息为whatSlotToEquip的位置和旋转信息 itemToAdd = Instantiate(Resources.Load<GameObject>(itemName), whatSlotToEquip.transform.position, whatSlotToEquip.transform.rotation); // 将新实例化的物体的父对象设置为whatSlotToEquip itemToAdd.transform.SetParent(whatSlotToEquip.transform); // 将物品名称添加到itemList中 itemList.Add(itemName); } //找到空闲的槽位 private GameObject FindNextEmptySlot() { // 遍历slotList列表中的每个槽位对象 foreach (GameObject slot in slotList) { // 如果槽位对象没有子对象(即为空闲) if (slot.transform.childCount == 0) { // 返回该槽位对象 return slot; } } // 如果没有找到空闲的槽位,则返回一个新的空游戏对象 return new GameObject(); } //判断库存是否已满 public bool CheckIfFull() { int counter = 0; // 遍历slotList列表中的每个槽位对象 foreach (GameObject slot in slotList) { // 如果槽位对象有子对象(即已被占用) if (slot.transform.childCount > 0) { counter++; } } // 如果占用的槽位数等于21(满了),返回true,否则返回false if (counter == 21) { return true; } else { return false; } } }
修改InteractableObject,调用添加库存功能
//拾取物品 public void PickUp(){ if(!InventorySystem.Instance.CheckIfFull()){ InventorySystem.Instance.AddToInventory(ItemName); Destroy(gameObject); }else{ Debug.Log("库存已满"); } }
把石头物品插槽样式放到Resources文件夹
效果
添加更多物品
比如我这里再加一些小树枝
https://sketchfab.com/3d-models/collection-of-forest-branches-11df981a8ff7441a9bdded31cc770e5f
效果
源码
源码不出意外的话我会放在最后一节