Unity3D RPG实现 2 —— 背包系统

视频链接:使用unity实现的3D RPG游戏_网络游戏热门视频 (bilibili.com)

目录

背包系统

制作背包的基本UI

快捷栏和人物信息面板的UI

创建世界地图上的可拾取物品

创建背包的数据库

实现背包根据数据库的信息显示物品

背包、格子、数据库的逻辑关系总结

实现拖拽物品

交换物品

实现切换武器

可使用的物品

显示人物面板相关信息

物品信息显示栏 

掉落物品

背包信息的存储

可拖拽的面板


背包系统

制作背包的基本UI

  • 导入素材设置画布布局

  • 使用 Grid Layout Group 进行网格化控制

  • 保存 Slot Holder 为 Prefab

下载两个素材:

 Unity3D RPG实现 2 —— 背包系统_第1张图片

然后导入设置画布:

Unity3D RPG实现 2 —— 背包系统_第2张图片

 Unity3D RPG实现 2 —— 背包系统_第3张图片

然后给图片设定长宽,注意到被拉伸后设置的不好看:

Unity3D RPG实现 2 —— 背包系统_第4张图片

进入原图的sprite editor:

调整其border,这样其就只会有中心被拉伸而边框不拉伸了。 

Unity3D RPG实现 2 —— 背包系统_第5张图片

点击apply,回到原panel中:

image type选择slice前后对比: 

Unity3D RPG实现 2 —— 背包系统_第6张图片

创建title和字、以及按钮:

Unity3D RPG实现 2 —— 背包系统_第7张图片

 接下来是关键,添加格子的办法:

先添加一个panel,锚点居中调整大小:

Unity3D RPG实现 2 —— 背包系统_第8张图片

阿尔法值设为0

然后添加这个便于布局:

Unity3D RPG实现 2 —— 背包系统_第9张图片

然后为其设定大小、空隙和初始:

Unity3D RPG实现 2 —— 背包系统_第10张图片

限定纵向列数为5

效果如图:Unity3D RPG实现 2 —— 背包系统_第11张图片

接下来在格子中间实现图片: 

添加图片,选择覆盖全屏的锚点,然后设定边距

Unity3D RPG实现 2 —— 背包系统_第12张图片

 效果:

随便选中一个图片可看到效果:

然后添加字体并设定大小。

然后将其设为预制体:

Unity3D RPG实现 2 —— 背包系统_第13张图片

然后复制30个:

Unity3D RPG实现 2 —— 背包系统_第14张图片

快捷栏和人物信息面板的UI

  • 创建快捷栏 Action Bar 的按钮 UI

  • 创建人物信息面板 UI

 添加画布,设定位置,然后添加这个layout Group

Unity3D RPG实现 2 —— 背包系统_第15张图片

然后将item slot拖出来也作为预制体:

 然后将item slot加到action button里去,将其设为预制体,添加脚本,和挂接组件

 Unity3D RPG实现 2 —— 背包系统_第16张图片

然后将其放进去actionbar的container脚本里:

Unity3D RPG实现 2 —— 背包系统_第17张图片

修改下图片使其美观:

Unity3D RPG实现 2 —— 背包系统_第18张图片

然后再创建一个画布如下:

Unity3D RPG实现 2 —— 背包系统_第19张图片

上面那一格拿来做武器。

在此基础上创建一个武器图片:

再给那个武器图片添加一个item slot  

Unity3D RPG实现 2 —— 背包系统_第20张图片

 添加武器和盾牌:

Unity3D RPG实现 2 —— 背包系统_第21张图片

 创建信息:

Unity3D RPG实现 2 —— 背包系统_第22张图片

创建世界地图上的可拾取物品

Unity3D RPG实现 2 —— 背包系统_第23张图片

这是人物身上的装备的地方

剑弄进来放在地上并导入材质

Unity3D RPG实现 2 —— 背包系统_第24张图片

然后将剑作为预制体并添加刚体碰撞体。

Unity3D RPG实现 2 —— 背包系统_第25张图片

然后创建一个SO:

[CreateAssetMenu(fileName ="New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{

}

 并创建文件:

Unity3D RPG实现 2 —— 背包系统_第26张图片

Unity3D RPG实现 2 —— 背包系统_第27张图片

此处需要将地图上的武器和玩家的武器区分开来,因为玩家身上的武器没有刚体组件,所以需要两个预制件。

public enum ItemType { Useable,Weapon,Armor}
[CreateAssetMenu(fileName ="New Item",menuName ="Inventory/Item Data")]
public class ItemData_SO : ScriptableObject
{
    public ItemType itemType;
    public string itemName;
    public Sprite itemIcon;
    public int itemAmount;//这个物品有多少数量

    [TextArea]
    public string description = "";

    public bool stackable;//是否可堆叠


    [Header("Weapon")]
    public GameObject WeaponPrefab;


}

设定剑信息

Unity3D RPG实现 2 —— 背包系统_第28张图片

接下来实现碰撞到世界地图上的剑然后装备。

Unity3D RPG实现 2 —— 背包系统_第29张图片

Unity3D RPG实现 2 —— 背包系统_第30张图片

public class ItemPickUp : MonoBehaviour
{
    public ItemData_SO itemData;

    private void OnTriggerEnter(Collider other)
    {
        if (other.CompareTag("Player"))
        {
            //TODO:添加物品到背包


            //装备武器
            Destroy(gameObject);
        }
    }
}

为实现给武器一个掉落到地面上的效果,添加重力和capsule collider:

Unity3D RPG实现 2 —— 背包系统_第31张图片

为使得剑在人物身上时可以正常,调位置:

由于我们希望装备武器后,武器攻击力也会改变,所以换武器的操作在characterStats完成。

设定位置:

Unity3D RPG实现 2 —— 背包系统_第32张图片

Unity3D RPG实现 2 —— 背包系统_第33张图片

然后即可实现触碰武器就装备上了。

接下来实现人物的数据由武器的数据决定。

先在物品信息处添加武器信息。

Unity3D RPG实现 2 —— 背包系统_第34张图片

添加武器数据

Unity3D RPG实现 2 —— 背包系统_第35张图片

修改数据的函数:(在攻击数据的SO里进行修改)

Unity3D RPG实现 2 —— 背包系统_第36张图片

装备武器时修改属性

Unity3D RPG实现 2 —— 背包系统_第37张图片

创建背包的数据库

内容:

  • 创建 InventoryData_SO 作为不同背包的数据库

  • 创建单独的 class InventoryItem 用来记录背包里的物品和现有数量

  • 添加 AddItem 方法将物品信息传递到 List 列表当中

  • 修改 ItemPickUp 实现碰撞物体 拾取到背包里

创建背包的SO

[CreateAssetMenu(fileName ="New Inventory",menuName ="Inventory/Inventory Data")]
public class InventoryData_SO : ScriptableObject
{

}

再创建一个文件夹:

Unity3D RPG实现 2 —— 背包系统_第38张图片

创建一个背包的inventory data,这个背包想要的无非就是一个列表,就是之前的itemData,但是如果创建一系列的itemData没有办法给它计数,因为此处的itemAmount指的是创建的物品本身的数量(比如打怪掉落苹果,掉落三个)

Unity3D RPG实现 2 —— 背包系统_第39张图片

如果直接把这个数据放到data里面用的话,就无法使用这个数据的相加或者相减了,所以此处为它单独创建一个列表里的元素,单独创建一个class。

实现如下:

public class InventoryData_SO : ScriptableObject
{
    public List items = new List();
}

[System.Serializable]
public class InventoryItem
{
    public ItemData_SO itemData;
    public int amount;

}

为使得类里面的变量能看见,添加Serializable使其序列化。

效果:

Unity3D RPG实现 2 —— 背包系统_第40张图片

堆叠物品的代码:

[CreateAssetMenu(fileName ="New Inventory",menuName ="Inventory/Inventory Data")]
public class InventoryData_SO : ScriptableObject
{
    public List items = new List();
    public void AddItem(ItemData_SO newItemData,int amount)
    {
        //对于添加物体,如果是可以堆叠的,只需要在背包中找到同类型的,改变数量,如果不能堆叠,就新建一个格子
        bool found = false;//是否找到同类物品
        //
        if (newItemData.stackable)
        {
            foreach(var item in items)
            {
                if (item.itemData == newItemData)
                {
                    item.amount += amount;
                    found = true;
                    break;
                }
            }
        }
        //上面用foreach是为了找相同的物品,下面使用for循环是为了找到最近的空格
        //如果捡到一个物体并且背包中不可堆叠只需要找到一个空格子放入即可
        for(int i = 0; i < items.Count; i++)
        {
            if (items[i].itemData == null && !found)
            {
                items[i].itemData = newItemData;
                items[i].amount = amount;
                break;
            }
        }
    }
}

这条添加物品的命令在哪里执行呢?我们创建了数据,接下来需要数据去管理,创建代码挂到inventory canvas管理。

添加背包数据:

Unity3D RPG实现 2 —— 背包系统_第41张图片

Unity3D RPG实现 2 —— 背包系统_第42张图片

拾取武器时不再是直接装备而是放入背包:

Unity3D RPG实现 2 —— 背包系统_第43张图片

效果:

Unity3D RPG实现 2 —— 背包系统_第44张图片

除此之外,有一点需要注意一下,

Unity3D RPG实现 2 —— 背包系统_第45张图片

在itemData_SO中,其自带一个amount信息,是这个物品初始就具有的数量,比如打怪一次掉落三个苹果。这时候这三个苹果就是一个整体。

由于有些物品是可堆叠的,对于可堆叠的物品就需要有一个数量,意思是背包中,这种类型的物品一共有多少个。

所以需要物品信息在此基础上,设定一个背包内的物品信息,将物品的数量和背包中物品的数量区分开:

Unity3D RPG实现 2 —— 背包系统_第46张图片

并将其作为一个新的类 

实现背包根据数据库的信息显示物品

  • 梳理逻辑并创建代码 ContainerUI / SlotHolder / ItemUI

  • 修改 SlotHolder 的 Prefab 添加空物体 ItemSlot

  • 逐层完成代码实现背包里正确显示物品图片和数量

创建三个脚本

 

为了实现代码的逻辑,调整下背包里物体的逻辑,背包里的格子的图片仅仅作为图片,改名为image,创建一个空物体用来挂接脚本,叫做item slot,并且把image赋值进去

 Unity3D RPG实现 2 —— 背包系统_第47张图片

此处实现将图片和数字更新进去

Unity3D RPG实现 2 —— 背包系统_第48张图片

  

 注意到初始时image为空,所以需要激活,为空时则关闭。

Unity3D RPG实现 2 —— 背包系统_第49张图片

 别忘了将image修改成它的子物体:

Unity3D RPG实现 2 —— 背包系统_第50张图片

然后在此处写这个脚本

Unity3D RPG实现 2 —— 背包系统_第51张图片

然后挂接上脚本;

Unity3D RPG实现 2 —— 背包系统_第52张图片


Item UI用子物体去赋值,它会自动获得子物体的item ui这个脚本:

Unity3D RPG实现 2 —— 背包系统_第53张图片

 在item ui创建两个变量:

Unity3D RPG实现 2 —— 背包系统_第54张图片

对不同情况进行分类,然后获取数据库中对应序号的物品,然后对其进行更新: 

Unity3D RPG实现 2 —— 背包系统_第55张图片

在父级物体上创建一个SlotHolder的容器:

Unity3D RPG实现 2 —— 背包系统_第56张图片

然后在inspector窗口中赋值。

Unity3D RPG实现 2 —— 背包系统_第57张图片

然后书写UI更新的代码:

Unity3D RPG实现 2 —— 背包系统_第58张图片

 再在总的管理器上添加一个获取的代码:

Unity3D RPG实现 2 —— 背包系统_第59张图片

 然后赋值:

Unity3D RPG实现 2 —— 背包系统_第60张图片

接下来实现通过InventoryManager来对背包里的物品进行更新:

初始时更新一次:

Unity3D RPG实现 2 —— 背包系统_第61张图片

捡到物品时再更新一次:

Unity3D RPG实现 2 —— 背包系统_第62张图片

别忘了给剑赋予图片 

Unity3D RPG实现 2 —— 背包系统_第63张图片

运行后发现数字不在正中间,解决办法:

因为之前多添加了一个空物体作为item slot,我们将这个item slot布局为覆盖全部,然后留下5个空余:

Unity3D RPG实现 2 —— 背包系统_第64张图片

将image设置为0:

Unity3D RPG实现 2 —— 背包系统_第65张图片

 然后拾取地上的剑即可看到剑出现在背包:

Unity3D RPG实现 2 —— 背包系统_第66张图片

人物去捡剑时也会出现

背包、格子、数据库的逻辑关系总结

 

此处介绍一下从上到下的逻辑:

Unity3D RPG实现 2 —— 背包系统_第67张图片

InventoryCanvas包含全部的背包系统,包括背包,人物属性和快捷栏。

InventoryBag包含背包的标题、关闭键和背包里的格子容器(InventoryContainer)。

格子容器(InventoryContainer)包含所有的子物体格子,代码上体现在:

并且由它执行子物体格子的刷新

Unity3D RPG实现 2 —— 背包系统_第68张图片

子物体SlotHolder的赋值通过在inspector窗口中进行:并且格子的顺序和ContainerUI中的List列表里的顺序是一致的。

Unity3D RPG实现 2 —— 背包系统_第69张图片

在SlotHolder中需要实现的代码如下:

SlotHolder是格子(由holder也可以理解),是不可移动的,是和容器里的List一一对应的。

而ItemSlot可以理解为格子里的物品,是可以移动,放到其他的格子里去的,并且里面的物品数量也可以改变。

但是其实ItemSlot里是item物品的UI信息,即图片和数量,并不是真正的物品。

真正的物品信息是放在这里的:

Unity3D RPG实现 2 —— 背包系统_第70张图片

然后

ItemData_SO长这样:

 实际例子比如剑:

在inspector窗口中为ContainerUI里的东西拖拽赋值后,接下来需要为格子里的每个物品UI进行序号上的赋值:

Unity3D RPG实现 2 —— 背包系统_第71张图片

并且还要让格子里进行物品UI上的更新,即:slotHolders[i].UpdateItem();

对于每个物品的UI信息,我们需要建立三个变量,获取的是Inventory管理器中的三个InvetoryData的SO物体:

Unity3D RPG实现 2 —— 背包系统_第72张图片

然后我们在itemUI的父级物体SlotHolder中根据类型来为其赋值:

Unity3D RPG实现 2 —— 背包系统_第73张图片

 这样的话,除了通过父物体去调用子物体,还实现了通过子物体获取父物体的方法。

上面这样给每个itemUI都赋予了不同种类的inventory栏,然后就可以通过该栏去获取该栏中与UI的Index对应的物品信息:

        var item = itemUI.Bag.items[itemUI.Index];//找到数据库中对应序号的对应物品

获取了该物品信息后,就可以进行更新了:

        itemUI.SetupItemUI(item.itemData, item.amount);

根据item信息更新itemUI的方法如下:

Unity3D RPG实现 2 —— 背包系统_第74张图片

总结一下就是说,我们实际上获取物品或者移动物品,事实上是先直接修改背包里的数据库的物品信息,修改完毕后,我们让背包中的每个物品栏里的UI去通过背包里的数据库获取对应index的物品信息,然后让物品栏中的UI信息更新为数据库中对应序号的物品的图片信息和数量。

实现拖拽物品

  • 创建 DragItem 实现拖拽功能

  • 介绍 EventSystems 里的接口

  • 实现拖拽跟随鼠标指针

  • 创建 DragData 用来记录每一个 UI 物品原始数据

给信息栏里的containerUI也添加:

Unity3D RPG实现 2 —— 背包系统_第75张图片

public class InventoryManager : Singleton
{

    [Header("Inventory Data")]
    //使用类似之前那样的模板,游戏新开始时复制一份的操作
    //TODO:最后添加模板用于保存数据
    public InventoryData_SO inventoryData;

    public InventoryData_SO actionData;
    public InventoryData_SO equipmentData;

    [Header("Containers")]
    public ContainerUI inventoryUI;
    public ContainerUI actionUI;
    public ContainerUI equipmentUI;

    private void Start()
    {
        inventoryUI.RefreshUI();
    }

}

 Unity3D RPG实现 2 —— 背包系统_第76张图片

 Unity3D RPG实现 2 —— 背包系统_第77张图片

Unity3D RPG实现 2 —— 背包系统_第78张图片

接下来实现拖拽功能:

为ItemSlot预制体添加该功能。

 想要实现拖拽的效果实现这几个接口即可

(交换数据往往需要一个第三方的东西变量来存储然后进行交换)

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

[RequireComponent(typeof(ItemUI))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    ItemUI currentItemUI;
    SlotHolder currentHolder;
    SlotHolder targetHolder;

    void Awake()
    {
        currentItemUI = GetComponent();
        currentHolder = GetComponentInParent();
    }
    public void OnBeginDrag(PointerEventData eventData)
    {
        //记录原始数据
    }

    public void OnDrag(PointerEventData eventData)
    {
        //跟随鼠标位置移动
        transform.position = eventData.position;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        //放下物品 交换数据
    }
}

然后即可实现拖拽移动物体,但是物体会被挡在背包里的格子之下。

解决方法:创建一个画布,当物体移动时,将该物体放到该画布上。

Unity3D RPG实现 2 —— 背包系统_第79张图片

dragItem:

Unity3D RPG实现 2 —— 背包系统_第80张图片

接下来需要用第三方的数据去保存那个holder,当拽完了之后需要还原到之前的位置

在InventoryManager设置:Unity3D RPG实现 2 —— 背包系统_第81张图片

并声明该变量:

Unity3D RPG实现 2 —— 背包系统_第82张图片

然后在拖拽时候修改: 

Unity3D RPG实现 2 —— 背包系统_第83张图片

交换物品

  • 使用 EventSystem 判断指针位置

  • 利用 RectTransformUtility.RectangleContainsScreenPoint 判断鼠标位置是否包含在每一个格子范围内

  • 交换图片的同时要交换真是的数据列表排序

  • 调整 RectTransform 的 offset 确保图片在正确位置显示

参考代码手册:

  • RectTransformUtility :点击跳转

  • RectTransform :点击跳转

  • EventSystem : 点击跳转

此处要用到event system

Unity3D RPG实现 2 —— 背包系统_第84张图片

需要要到event system的这个变量:

我们需要判断被拖拽的物品是否在三个栏里面

书写函数:

    public bool CheckInInventoryUI(Vector3 position)//此处这个位置是要传输进来的位置
    {
        for(int i = 0; i < inventoryUI.slotHolders.Length; i++)
        {
            RectTransform t = inventoryUI.slotHolders[i].transform as RectTransform;//强制类型转换
            if (RectTransformUtility.RectangleContainsScreenPoint(t, position))//判断当前位置是否物品栏里
            {
                return true;
            }
            
        }
        return false;
    }

 使用如下:

    public void OnEndDrag(PointerEventData eventData)
    {
        //放下物品 交换数据
        if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
        {
            if(InventoryManager.Instance.CheckInActionUI(eventData.position)
                || InventoryManager.Instance.CheckInInventoryUI(eventData.position)
                || InventoryManager.Instance.CheckInEquipmentUI(eventData.position))
                //判断是否在三个栏里面的格子里
            {

            }
        }
    }
}

下面这个是判断是否在UI组件上

Unity3D RPG实现 2 —— 背包系统_第85张图片

想要实现交换,在endDrag的函数里实现,首先看它是否指向UI组件,如果是的话看是否在设定的背包、快捷栏、人物信息里。

如果在的话,我们获取鼠标点击松开位置的该物品信息。

如果该物体和原来鼠标拖拽的物体一样且可堆叠,只需要增加数字即可。

如果不可堆叠,那就交换数据库中的物品信息,然后刷新UI,就实现了这个效果。

实现代码:

[RequireComponent(typeof(ItemUI))]
public class DragItem : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler
{
    ItemUI currentItemUI;
    //
    SlotHolder currentHolder;
    SlotHolder targetHolder;

    void Awake()
    {
        currentItemUI = GetComponent();
        currentHolder = GetComponentInParent();//原先的格子的信息
    }
    public void OnBeginDrag(PointerEventData eventData)
    {
        InventoryManager.Instance.currentDrag = new InventoryManager.DragData();
        InventoryManager.Instance.currentDrag.orginalHolder = GetComponentInParent();
        InventoryManager.Instance.currentDrag.originalParent = (RectTransform)transform.parent;
        //记录原始数据
        transform.SetParent(InventoryManager.Instance.dragCanvas.transform,true);
    }

    public void OnDrag(PointerEventData eventData)
    {
        //跟随鼠标位置移动
        transform.position = eventData.position;
    }

    public void OnEndDrag(PointerEventData eventData)
    {
        //放下物品 交换数据
        if (EventSystem.current.IsPointerOverGameObject())//是否指向UI组件
        {
            if(InventoryManager.Instance.CheckInActionUI(eventData.position)
                || InventoryManager.Instance.CheckInInventoryUI(eventData.position)
                || InventoryManager.Instance.CheckInEquipmentUI(eventData.position))
                //判断是否在三个栏里面的格子里
            {
                if (eventData.pointerEnter.gameObject.GetComponent())
                    targetHolder = eventData.pointerEnter.gameObject.GetComponent();
                else
                    targetHolder = eventData.pointerEnter.gameObject.GetComponentInParent();
                //如果没找到,此时是因为被图片所挡住了,那么就获取其父类的component

                Debug.Log(eventData.pointerEnter.gameObject);
                //判断鼠标选中的物体是否有slot holder,


                //由于例如食物不能放在武器栏里,所以需要对其做区分
                switch (targetHolder.slotType)
                {
                    case SlotType.BAG:
                        SwapItem();
                        break;
                         
                    case SlotType.WEAPON:
                        break;

                    case SlotType.ARMOR:
                        break;

                    case SlotType.ACTION:
                        break;
                   
                }
                
                currentHolder.UpdateItem();//交换完毕后需要更新数据
                targetHolder.UpdateItem();


            }
        }

        transform.SetParent(InventoryManager.Instance.currentDrag.originalParent);

    }

    public void SwapItem()
    {
        //targetHolder是鼠标指向的位置的格子。
        //获取目标格子上面显示的UI,UI图片对应它身上属于哪个背包的哪一个序号的物品
        var targetItem = targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index];//实际上就是获取鼠标指向的物品item

        var tempItem = currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index];//点击前的鼠标的格子的信息

        //如果是相同的物品就进行合并
        bool isSameItem = tempItem.itemData == targetItem.itemData;
        if (isSameItem && targetItem.itemData.stackable)//并且还要可堆叠才行
        {
            targetItem.amount += tempItem.amount;
            tempItem.itemData = null;
            tempItem.amount = 0;
        }
        else
        {
            currentHolder.itemUI.Bag.items[currentHolder.itemUI.Index] = targetItem;
            //targetItem=tempItem;这样的写法不行因为targetItem只是我们获取的一个变量,应该直接用其本身去更换,即下一行的写法
            targetHolder.itemUI.Bag.items[targetHolder.itemUI.Index] = tempItem;
        }
    }
}

 然后接下来如果我们移动物体,如果正好移到中心方块则可以正常实现,如果没有移到中心方块则会出现下面这样的情况。

观察可得,此时物体卡在中间:

Unity3D RPG实现 2 —— 背包系统_第86张图片

item slot的位置也发生了变化:

Unity3D RPG实现 2 —— 背包系统_第87张图片

所以我们要保证它能回到间距5个单位的中心格子上。

 Unity3D RPG实现 2 —— 背包系统_第88张图片

 注意这两个变量:

offsetMax就是 这个里面的right和top

 然后在代码中新增这两项:

Unity3D RPG实现 2 —— 背包系统_第89张图片

接下来即可实现拖拽。 

 Unity3D RPG实现 2 —— 背包系统_第90张图片

实现切换武器

DragItem 中添加物品属性和 SlotType 的匹配

对于非背包里的物品我们也可以交换但是要加以限制。

Unity3D RPG实现 2 —— 背包系统_第91张图片

在 SlotHolder 中切换所属背包的数据库 

当我们将Slot Holder放在不同的地方时,此时也要更改其所属的背包对应的数据库:

Unity3D RPG实现 2 —— 背包系统_第92张图片

这样即可实现不同栏间的物品交换,并且数据库也会因此更新。

鼠标点击会使得人物移动的问题

 Unity3D RPG实现 2 —— 背包系统_第93张图片

 换武器的方法

在characterStats中补充卸下武器的代码:

    public void UnEquipWeapon()
    {
        if (weaponSlot.transform.childCount != 0)
        {
            for(int i = 0; i < weaponSlot.transform.childCount; i++)
            {
                Destroy(weaponSlot.transform.GetChild(i).gameObject);
            }
        }
    }

卸下武器前就要还原成原来的攻击数据,因此需要有一个变量来记录初始时没武器的数据:

Unity3D RPG实现 2 —— 背包系统_第94张图片

代码如下:

    public void UnEquipWeapon()
    {
        if (weaponSlot.transform.childCount != 0)
        {
            for(int i = 0; i < weaponSlot.transform.childCount; i++)
            {
                Destroy(weaponSlot.transform.GetChild(i).gameObject);
            }
        }
        attackData.ApplyWeanponData(baseAttackData);//卸下武器后还原之前的攻击力
        //TODO:切换动画

    }

    public void ChangeWeapon(ItemData_SO weapon)
    {
        UnEquipWeapon();
        EquipWeapon(weapon);
    }

然后在SlotHolder更新一栏中更改武器:

Unity3D RPG实现 2 —— 背包系统_第95张图片

 

可使用的物品

  • 创建大蘑菇 并添加必要的组件

  • 创建 UseableItemData_SO 制作可使用物品的属性模版

  • 利用 IPointerClickHandler 接口实现双击使用物品

  • 在 CharacterStats 中添加 ApplyHealth 实现血量变化

参考代码手册:

  • IPointerClickHandler :点击跳转

创建一个蘑菇的变量:

Unity3D RPG实现 2 —— 背包系统_第96张图片

创建useable类型的SO:

Unity3D RPG实现 2 —— 背包系统_第97张图片

然后在itemData中加入这种类型: 

Unity3D RPG实现 2 —— 背包系统_第98张图片

 创建useable类型的SO:

Unity3D RPG实现 2 —— 背包系统_第99张图片

并赋值:(为useable Item赋值)

Unity3D RPG实现 2 —— 背包系统_第100张图片

然后在场景中创建一个mushroom并添加这些组件:
Unity3D RPG实现 2 —— 背包系统_第101张图片

 

之后即可实现拾取蘑菇并且在格子和快捷栏里自由移动。

 ​

接下来实现使用物品的功能:

先在itemUI中写一个根据对应UI获取对应物品的方法:

Unity3D RPG实现 2 —— 背包系统_第102张图片

在characterStats中书写一个函数:

Unity3D RPG实现 2 —— 背包系统_第103张图片

  使用需要实现一个接口,Unity3D RPG实现 2 —— 背包系统_第104张图片​ 

 在SlotHolder里实现该接口

    public void OnPointerClick(PointerEventData eventData)
    {
        if (eventData.clickCount % 2 == 0)//代表是双击的话
        {
            UseItem();
        }
    }
    public void UseItem()
    {
        if (itemUI.GetItem().itemType == ItemType.Useable&&itemUI.Bag.items[itemUI.Index].amount>0)
        {
            GameManager.Instance.playerStats.ApplyHealth(itemUI.GetItem().useableData.healthPoint);
            itemUI.Bag.items[itemUI.Index].amount -= 1;
        }
        UpdateItem();
    }

别忘了要进行数量减一,更新UI的操作。

但是之后发现一个bug,当蘑菇用完时图片仍然存在:

 Unity3D RPG实现 2 —— 背包系统_第105张图片

解决方法:

Unity3D RPG实现 2 —— 背包系统_第106张图片

显示人物面板相关信息

设定一个相机让它观察player,并设定好各类信息:

创建texture然后放进去

Unity3D RPG实现 2 —— 背包系统_第107张图片

然后将renderTexture给Stats里面创建的RawImage

Unity3D RPG实现 2 —— 背包系统_第108张图片

 记得将相机的设置放入prefab里面  

Unity3D RPG实现 2 —— 背包系统_第109张图片

然后可以显示:

接下来实现血量和伤害的显示: 

Unity3D RPG实现 2 —— 背包系统_第110张图片

然后在装备武器时进行信息的更新:

Unity3D RPG实现 2 —— 背包系统_第111张图片

也可以在manager中更新:
Unity3D RPG实现 2 —— 背包系统_第112张图片

随后即可实现血量和攻击的更新了:升级时也会改变

上面完成了武器的装载与拆卸,接下来实现不同武器的切换。

切换到不同武器时,对应的动画控制器也应该相应做出更改。

根据不同武器切换动画控制器

  • 使用更多动画来创建 Animator override controller

  • ItemData_SO 中添加武器配套动画控制器

  • 在 EquipWeapon 和 UnEquipment 中添加代码切换武器伴随动画

参考素材:

  • RPG Character Animation Pack Free :点击跳转

 这两个作者的素材很好,有很多动作素材

下载这个只保留animation即可:

Unity3D RPG实现 2 —— 背包系统_第113张图片

创建一个没武器的override controller:并导入动画

Unity3D RPG实现 2 —— 背包系统_第114张图片

然后即可实现无武器的运行。

但是会有两个报错,是因为动画自带两个事件。删除即可。

Unity3D RPG实现 2 —— 背包系统_第115张图片

我们希望给武器自己加上一个animation controller:

Unity3D RPG实现 2 —— 背包系统_第116张图片

 Unity3D RPG实现 2 —— 背包系统_第117张图片

在此处更换装备。

初始时获取初始动画控制器:

Unity3D RPG实现 2 —— 背包系统_第118张图片

 卸武器时候还原:

 Unity3D RPG实现 2 —— 背包系统_第119张图片

这样就可以自由切换了。

有个问题在于人物的武器不能在属性中显示,将sword的预制体的layer设置为player即可。 

物品信息显示栏 

创建一个image,调整大小:

Unity3D RPG实现 2 —— 背包系统_第120张图片

然后设定为了能根据不同的设置出不同的大小,添加这个组件

Unity3D RPG实现 2 —— 背包系统_第121张图片

添加这个:

Unity3D RPG实现 2 —— 背包系统_第122张图片

Unity3D RPG实现 2 —— 背包系统_第123张图片

给信息一栏也添加Fitter:

Unity3D RPG实现 2 —— 背包系统_第124张图片

这样就可以实现UI的信息的多少会随着栏的信息而改变:

Unity3D RPG实现 2 —— 背包系统_第125张图片

 创建代码,实现控制:

Unity3D RPG实现 2 —— 背包系统_第126张图片

鼠标的悬停希望实现的接口:

Unity3D RPG实现 2 —— 背包系统_第127张图片

让SlotHolder实现这两个接口:

    public void OnPointerEnter(PointerEventData eventData)
    {
        if (itemUI.GetItem())
        {
            InventoryManager.Instance.tooltip.SetupTooltip(itemUI.GetItem());
            InventoryManager.Instance.tooltip.gameObject.SetActive(true);
        }
    }

    public void OnPointerExit(PointerEventData eventData)
    {
        InventoryManager.Instance.tooltip.gameObject.SetActive(false);
    }

 这样就可以实现悬停时出现。

接下来希望悬停时出现的位置是鼠标的位置:

Unity3D RPG实现 2 —— 背包系统_第128张图片

效果:

Unity3D RPG实现 2 —— 背包系统_第129张图片​但是出现的位置是在鼠标中心,并且还会闪烁。

是因为锚点是出现在中心:

Unity3D RPG实现 2 —— 背包系统_第130张图片

 修改方法:

Unity3D RPG实现 2 —— 背包系统_第131张图片

Unity3D RPG实现 2 —— 背包系统_第132张图片

 代码如下:

public class ItemToolTip : MonoBehaviour
{
    public Text itemNameText;
    public Text itemInfoText;
    RectTransform rectTransform;

    private void Awake()
    {
        rectTransform = GetComponent();
    }
    public void SetupTooltip(ItemData_SO item)
    {
        itemNameText.text = item.itemName;
        itemInfoText.text = item.description;
    }

    void OnEnable()
    {
        //开始时会闪烁是因为没有定位好坐标,开始时先更新一下即可避免这种效果
        UpdatePosition();

    }
    private void Update()
    {
        UpdatePosition();
    }
    public void UpdatePosition()
    {
        Vector3 mousePos = Input.mousePosition;
        rectTransform.position = mousePos;

        Vector3[] corners = new Vector3[4];
        rectTransform.GetWorldCorners(corners);

        float width = corners[3].x - corners[0].x;
        float height = corners[1].y - corners[0].y;

        if (mousePos.y < height)
            rectTransform.position = mousePos + Vector3.up * height * 0.6f;
        else if (Screen.width - mousePos.x > width)
            rectTransform.position = mousePos + Vector3.right * width * 0.6f;
        else
            rectTransform.position = mousePos + Vector3.left * width * 0.6f;

    }

}

还有个问题在于当你背包关闭时,信息仍然有显示,解决方法:

Unity3D RPG实现 2 —— 背包系统_第133张图片

Unity3D RPG实现 2 —— 背包系统_第134张图片

掉落物品

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

public class LootSpawner : MonoBehaviour
{
    [System.Serializable]
    public class LootItem
    {
        public GameObject item;
        [Range(0, 1)]
        public float weight;
    }

    public LootItem[] lootItems; 

    public void Spawnloot()
    {
        float currentValue = Random.value;

        for(int i = 0; i < lootItems.Length; i++)
        {
            if (currentValue <= lootItems[i].weight)
            {
                GameObject obj = Instantiate(lootItems[i].item);
                obj.transform.position = transform.position + Vector3.up * 2;
            }
        }
    }
}

给怪物添加以上代码 

当敌人死亡时调用掉落:

Unity3D RPG实现 2 —— 背包系统_第135张图片

这样即可实现掉落 

背包信息的存储

Unity3D RPG实现 2 —— 背包系统_第136张图片

创建模板

Unity3D RPG实现 2 —— 背包系统_第137张图片

保存加载数据:

Unity3D RPG实现 2 —— 背包系统_第138张图片

开始时加载数据:

Unity3D RPG实现 2 —— 背包系统_第139张图片

​ Unity3D RPG实现 2 —— 背包系统_第140张图片

然后在inventory manager的inspector中修改:

Unity3D RPG实现 2 —— 背包系统_第141张图片


 

如果我们需要在第二个场景中也使用背包系统,就将其作为预制体在第二个场景中载入:

并且记得添加eventSystem、

 这样即可实现场景切换时保存的效果。

Unity3D RPG实现 2 —— 背包系统_第142张图片

可拖拽的面板

实现拖拽面板

 给两个背包面板添加这个代码:

public class DragPanel : MonoBehaviour,IDragHandler
{
    RectTransform rectTransform;

    void Awake()
    {
        rectTransform = GetComponent();
    }
    public void OnDrag(PointerEventData eventData)
    {
        rectTransform.anchoredPosition += eventData.delta;//让其锚点的位置的改变量和鼠标的改变量相同即可
    }
}

即可实现拖拽。

但是有一点问题在于如果鼠标处于背包的边边部分进行拖拽时,此时面板的位移增量就比鼠标的位移增量要大了。

是因为面板中这个组件的存在:

Unity3D RPG实现 2 —— 背包系统_第143张图片

         

 方法:

Unity3D RPG实现 2 —— 背包系统_第144张图片

 Unity3D RPG实现 2 —— 背包系统_第145张图片

但是移动背包时会被另外一个背包挡住:

Unity3D RPG实现 2 —— 背包系统_第146张图片

 观察可发现是因为顺序在下的被后渲染,后渲染的就会显示在前方

Unity3D RPG实现 2 —— 背包系统_第147张图片

使用这个方法将其放在索引的最下方。

但是有一点,不能将其放在drag canvas索引的下方。要保证drag canvas始终在下方:

Unity3D RPG实现 2 —— 背包系统_第148张图片

所以需要使用获取索引的方法。

将其索引设置在2即可。

Unity3D RPG实现 2 —— 背包系统_第149张图片

这样就不会被遮挡。

但是此处又出现了一个新的问题,问题在于将物品拖拽放回原处的时候,背包里就看不见了。

解决方法在此处添加:

Unity3D RPG实现 2 —— 背包系统_第150张图片

 效果:

你可能感兴趣的:(unity游戏开发,unity,3d,c#)