【在Unity中创建生存游戏 】4~6

Day 3(2)


操作


补充上一次出现的bug:将兔子的rigibody组件中的绕x/z轴旋转冻结掉

资产:教程里用的那个要收费了,随便了换一个

Low poly rocks collection | 3D 外观 | Unity Asset Store

添加一些可以互动的石头,我们想要做到

1)拾取

2)将显示文本和拾取功能限制在一定距离内(代码部分的修改见后面的代码部分)

  • 将玩家对象的标签设为Player

  • 为石头添加另外一个box collider最为范围检测的碰撞体(设计的大一点!),这个碰撞体应当充当一个检测区域,所以不应该有实际的碰撞效果(is Trigger).与教程不同的是,我在添加第二个box collider时候无法重新设置碰撞体积,最终选择了Capsule Collider。

  • 我们不希望碰撞检测的射线碰到is Trigger的碰撞器,设置路径:Project Settings –>Physics –>Queries Hit Triggers设置为false

  • 添加拾取方法(简单销毁),遇到问题:当人物在范围内时准心只需要碰到is Trigger的碰撞器就可以拾取对象解决办法:跟踪我们的准心是否在那个较小的Box Collider上.在SelectionManager 中添加onTarget属性,把它的真假值与文本出现与否的检测相对应,为了Interactable组件可以获取到electionManager 中的onTarget属性,我们让SelectionManager变成一个单例(不懂)

代码


 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 ​
 public class InteractableObject : MonoBehaviour
 {
     // Start is called before the first frame update
     public bool playerInRange;
 ​
     public string ItermName = "?_?";
 ​
     private void Update()
     {
         if(Input.GetKeyDown(KeyCode.E)&&playerInRange&&SelectionManager.instance.onTarget)
         {
             Debug.Log("item added to inventory");//暂时还没有背包
             Destroy(gameObject);
         }
     }
 ​
 ​
     public string GetItemName()
     {
 ​
         return ItermName;
     }
 ​
 ​
 ​
     //进入检测范围,将playerInRange置为真
     private void OnTriggerEnter(Collider other)
     {
         //j仅对标签为玩家的对象做出反应
         if(other.CompareTag("Player"))
         {
             playerInRange = true;
         }
     }
 ​
     private void OnTriggerExit(Collider other)
     {
         if (other.CompareTag("Player"))
         {
             playerInRange = false;
         }
     }
 }

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.UI;
 ​
 public class SelectionManager : MonoBehaviour
 {
     //让SelectionManager变成一个单例,为了实现这一目的还创建了awak方法,不懂
     public static SelectionManager instance { get; set; }
     public bool onTarget;
 ​
     public GameObject interaction_Info_UI;//声明一个UI交互界面,绑定之前
     Text interaction_text;//声明组件文本,用于获取UI交互界面的文本组件
 ​
     private void Start()
     {
         onTarget = false;
         interaction_text = interaction_Info_UI.GetComponent<Text>();//获取UI交互界面的文本组件
     }
 ​
     void Update()
     {
         Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);//从屏幕中心向鼠标位置发射射线(之前把鼠标位置固定在屏幕的正中间)
         RaycastHit hit;//射线命中信息
         if (Physics.Raycast(ray, out hit))//如果射线击中了,射线命中信息hit存储相关信息
         {
             var selectionTransform = hit.transform;//获取被击中单位的位置
 ​
             InteractableObject ourInteractable = selectionTransform.GetComponent<InteractableObject>();
 ​
             if (ourInteractable&&ourInteractable.playerInRange)//如果被击中单位时可互动对象InteractableObject才继续 2/这里进行过修改,保证角色进入一定范围才显示
             {
                 onTarget = true;
 ​
                 interaction_text.text = selectionTransform.GetComponent<InteractableObject>().GetItemName();//修改UI交互界面的文本为可互动对象的名字
                 interaction_Info_UI.SetActive(true);//把先前禁用的UI交互界面打开,以至于我们在屏幕上可以看到文本信息
             }
             else
             {
                 onTarget = false;
                 interaction_Info_UI.SetActive(false);//反之再次禁用UI交互界面
             }
 ​
         }
         else
         {
             onTarget = false;
             interaction_Info_UI.SetActive(false);//如果射线没有击中,也禁用UI交互界面
         }
     }
 ​
     private void Awake()
     {
         if(instance != null&& instance !=this)
         {
             Destroy(gameObject);
         }
         else
         {
             instance = this;
         }
     }
 }

tip:之前在Terrain上放的树和动物所在的组的坐标都不是(0,0,0),调位置时候麻烦炸了,以后分组的时候要注意。

Day 4(1)


目标:创建背包与物品显示

操作


在Canvas层下创建Image层并命名为InventoryScreen(库存屏幕),其下创建多个Image类型的背包格并命名为Slot(插槽),其下创建一个Image类型的收集品来显示物品图片并命名为Collection。在每一个Slot中添加脚本ItemSlot,在Collection中添加脚本DragDrop并把其上层的Canvas层作为目标拖入到脚本中。

设计思路

1)库存系统InventorySystem

控制背包UI的打开与关闭,并保证在背包打开时鼠标光标重新出现使得玩家可以拖动物品

2)拖拽脚本DragDrop :MonoBehaviour,IBeginDragHandler,IEndDragHandler,IDragHandler

主要通过OnBeginDrag(),OnDrag(),OnEndDrag()这三个方法来实现需要的不同时期拖动的效果

3)插槽ItemSlot : MonoBehaviour, IDropHandler

实现物品对父级的修改

详情见注释

代码


 using System;
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 ​
 public class InventorySystem : MonoBehaviour
 {
 ​
     public static InventorySystem Instance { get; set; }
 ​
     public GameObject inventoryScreenUI;
     public bool isOpen;
 ​
 ​
     private void Awake()
     {
         if (Instance != null && Instance != this)
         {
             Destroy(gameObject);
         }
         else
         {
             Instance = this;
         }
     }
 ​
 ​
     void Start()
     {
         isOpen = false;
     }
 ​
 ​
     void Update()
     {
 ​
         if (Input.GetKeyDown(KeyCode.I) && !isOpen)
         {
 ​
             Debug.Log("I is pressed");
             inventoryScreenUI.SetActive(true);//唤醒库存UI
             Cursor.lockState = CursorLockMode.None;//不再锁定鼠标光标
             isOpen = true;
 ​
         }
         else if (Input.GetKeyDown(KeyCode.I) && isOpen)
         {
             inventoryScreenUI.SetActive(false);//再次点击关闭UI
             Cursor.lockState = CursorLockMode.Locked;//锁定鼠标光标
             isOpen = false;
         }
     }
 }
 ​

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.EventSystems;
 using UnityEngine.UI;
 ​
 public class DragDrop : MonoBehaviour, IBeginDragHandler, IEndDragHandler, IDragHandler
 {
 ​
     //[SerializeField] private Canvas canvas;//对上层对象Canvas的引用
     private RectTransform rectTransform;//矩阵位置,相对于画布的位置
     private CanvasGroup canvasGroup;//画布组,这个组件用于保证在我们点击当前游戏对象时它可以与它上层的插槽格分离
 ​
     public static GameObject itemBeingDragged;
     Vector3 startPosition;
     Transform startParent;//记录初始父级(原来所处的插槽),之后如果拖拽失败了,游戏对象还要回到初始父级
 ​
 ​
 ​
     private void Awake()
     {
 ​
         rectTransform = GetComponent<RectTransform>();
         canvasGroup = GetComponent<CanvasGroup>();//记得在挂载的对象上添加这个组件,不然会报错
 ​
     }
 ​
     //开始拖拽
     public void OnBeginDrag(PointerEventData eventData)
     {
 ​
         Debug.Log("OnBeginDrag");
         canvasGroup.alpha = 0.6f;//修改alpha值,实现当我们开始拖拽时物品图标会变得有些透明
         canvasGroup.blocksRaycasts = false; //So the ray cast will ignore the item itself.之前我们有一个射线来着,检测碰撞来获取物品信息等的,现在让射线碰撞忽视这个对象
         startPosition = transform.position;
         startParent = transform.parent;
         transform.SetParent(transform.root);//开始拖拽时,游戏对象从一开始的作为插槽Slot的子元素变成了库存屏幕InventoryScreen的子元素
         itemBeingDragged = gameObject;
 ​
     }
 ​
     //正在拖拽
     public void OnDrag(PointerEventData eventData)
     {
         //So the item will move with our mouse (at same speed)  and so it will be consistant if the canvas has a different scale (other then 1);
         rectTransform.anchoredPosition += eventData.delta;//拖动速度与鼠标速度一致  除以画布缩放倍率可以避免一些bug/canvas.scaleFactor,但这导致实例化物品图标时候会有bug,所以先注释掉
 ​
     }
 ​
 ​
     //结束拖拽
     public void OnEndDrag(PointerEventData eventData)
     {
 ​
         itemBeingDragged = null;
 ​
         //如果移动后父级还是原来那个或者超出了移动范围成为了库存屏幕InventoryScreen的子元素,那么该游戏对象的父级还是原来那个
         if (transform.parent == startParent || transform.parent == transform.root)
         {
             transform.position = startPosition;
             transform.SetParent(startParent);
 ​
         }
 ​
         Debug.Log("OnEndDrag");
         canvasGroup.alpha = 1f;
         canvasGroup.blocksRaycasts = true;
     }
 ​
 ​
 ​
 }

 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 using UnityEngine.EventSystems;
 ​
 public class ItemSlot : MonoBehaviour, IDropHandler
 {
     public GameObject Item
     {
         get
         {
             if (transform.childCount > 0)
             {
                 return transform.GetChild(0).gameObject;
             }
             return null;
         }
     }
 ​

     public void OnDrop(PointerEventData eventData)
     {
         Debug.Log("OnDrop");
 ​
         //if there is not item already then set our item.如果选择的新父级没有子元素
         if (!Item)
         {
 ​
             DragDrop.itemBeingDragged.transform.SetParent(transform);//设置新的父级
             DragDrop.itemBeingDragged.transform.localPosition = new Vector2(0, 0);//重设相对位置,让对象图片显示在Slot的正中间
         }
 ​
 ​
     }
 }

Day 4(2)


目标:实现拾取物品并添加到库存系统(背包)中

操作


当捡起一个物品,调用InventorySystem.Instance.AddToInventory(itemName);将拾取物品的名字传入到库存系统中,库存系统会检查背包是否已经满了,如果没满的话,用whatSlotToEquip = FindNextEmptySlot();找到一个合适的插槽来放入物品,物品的对象需要经过实例化(如下)

//实例化想要添加到背包内的对象

itemToAdd = Instantiate(Resources.Load<GameObject>(itemName), whatSlotToEquip.transform.position, whatSlotToEquip.transform.rotation);

itemToAdd.transform.SetParent(whatSlotToEquip.transform);

itemList.Add(itemName);

  • 拾取效果的实现是传入拾取物品的名字,在资源库内搜索同样名字的预制体并实例化,然后将那个预制体移动到指定的父类之下

  • 只有在背包没满的前提下才可以拾取,调用库存系统相应的方法来实现

  • 查找空的插槽用的是遍历

  • 之前的拾取代码参考SelectionManager的onTarget来判断拾取,这会导致相邻的两块石头会被同时拾起(即使它们一前一后),修改为把onTarget属性移到InteractableObject,由每一个石头单独判断自己是否被选中了

代码


 using System;
 using System.Collections;
 using System.Collections.Generic;
 using UnityEngine;
 ​
 public class InventorySystem : MonoBehaviour
 {
 ​
     //令InventorySystem成为一个单例子(在游戏运行中一直存在且仅存在一个),把这个脚本的所有public类型的属性和脚本公开给所有脚本使用//希望理解的是对的
     public static InventorySystem Instance { get; set; }
 ​
     public GameObject inventoryScreenUI;//背包UI
 ​
     public List<GameObject> slotList = new List<GameObject>();//获取所有slot
 ​
     public List<String> itemList = new List<string>();//项目列表,获取被添加进背包的游戏对象名称
 ​
     private GameObject itemToAdd;//将要加入背包的游戏对象
     private GameObject whatSlotToEquip;//将要被游戏对象插入的slot
 ​
     public bool isOpen;//背包被打开
 ​
     public bool isFull;//背包已满
 ​
     private void Awake()
     {
         if (Instance != null && Instance != this)
         {
             Destroy(gameObject);
         }
         else
         {
             Instance = this;
         }
     }
 ​
 ​
     void Start()
     {
         isOpen = false;
         isFull = false;
         PopulateSlotList();//可以直接打出这个方法,用alt+enter自动生成相应的方法
     }
 ​
     //初始化背包插槽的列表
     private void PopulateSlotList()
     {
         foreach(Transform child in inventoryScreenUI.transform)
         {
             if(child.CompareTag("Slot"))
             {
                 slotList.Add(child.gameObject);
             }
         }
     }
 ​
     //根据名字添加游戏对象的预制体
     public void AddToInventory(string itemName)
     {
        if(isFull == false)//在背包没满的情况下添加,在没有逻辑错误情况下这个if总会通过,可以保留用以检查bug
         {
             whatSlotToEquip = FindNextEmptySlot();//找到空插槽
 ​
             //实例化想要添加到背包内的对象(根据名字实例对象,位置,转角)
             itemToAdd = Instantiate(Resources.Load<GameObject>(itemName), whatSlotToEquip.transform.position, whatSlotToEquip.transform.rotation);
             itemToAdd.transform.SetParent(whatSlotToEquip.transform);//确定游戏对象的父级
 ​
             itemList.Add(itemName);//把这个名字添加到项目列表中
         }
     }
 ​
     //找到下一个空的插槽,理论上调用这个方法的时候一定是有空插槽的
     private GameObject FindNextEmptySlot()
     {
         //在插槽列表中搜索一个空的插槽
         foreach(GameObject slot in slotList)
         {
             if (slot.transform.childCount == 0)//一个子元素都没就是空插槽
                 return slot;
         }
         return new GameObject();//一般不会由这个返回
     }
 ​
     void Update()
     {
 ​
         if (Input.GetKeyDown(KeyCode.I) && !isOpen)
         {
             inventoryScreenUI.SetActive(true);//唤醒库存UI
             Cursor.lockState = CursorLockMode.None;//不再锁定鼠标光标
             isOpen = true;
         }
         else if (Input.GetKeyDown(KeyCode.I) && isOpen)
         {
             inventoryScreenUI.SetActive(false);//再次点击关闭UI
             Cursor.lockState = CursorLockMode.Locked;//锁定鼠标光标
             isOpen = false;
         }
     }
 ​
     //遍历查找背包中的空插槽
     public bool CheckIfFull()
     {
         int count = 0;
         if (isFull)
             return true;
         foreach(GameObject slot in slotList)
         {
             if(slot.transform.childCount > 0)
             {
                 count++;
             }
         }
 ​
         if(count == 21)//21是目前的最大背包容量,可修改
         {
             isFull = true;
             return true;
         }
         else
         {
             return false;
         }
     }
 }

tip:设置UI界面的时候尽量不要去缩放Scale属性,之后再去调整图片大小什么很麻烦。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值