Siki_Unity_3-3_背包系统

Posted FudgeBear

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Siki_Unity_3-3_背包系统相关的知识,希望对你有一定的参考价值。

Unity 3-3 背包系统(基于UGUI)

任务1&2&3:演示、介绍、类图分析

背包面板、箱子面板、锻造合成面板、装备佩戴面板、商店面板等

面板的显示和隐藏、保存和加载、拾起物品、物品移动、物品出售和购买等

导入素材UI.unitypackage

UML图设计:

物品Item分为几类:消耗品Consumable、装备Equipment、武器Weapon、材料Material
  消耗品影响HP/MP
  装备影响strength/ intelligence/ agility/ stamina等
    装备类型有:head/ neck/ chest/ ring/ leg/ bracer/ boots/ shoulder/ belt/ offHand
  武器影响damage
    武器类型有:offHand/ mainHand
  材料用于合成装备和武器

  物品共有变量:
    id/ name/ type/ quality/ description/ capacity/ buyprice/ sellprice
    消耗品变量:
      hp/ mp
    装备变量:
      strength/ intelligence/ agility/ stamina等/ 还有equipmentType
    武器变量:
      damage/ 还有weaponType
    材料变量:无

任务5&6:开发Item类(根据类图创建类)

使用get;set;的方式,可以很灵活地控制变量的访问权限

public class Item {
    public int ID { get; set; }
    public string Name { get; set; }
    public ItemType Type { get; set; }
    public ItemQuality Quality { get; set; }
    public string Description { get; set; }
    public int Capacity { get; set; }
    public int buyprice { get; set; }
    public int sellprice { get; set; }

    public Item(int id, string name, ItemType type, ItemQuality quality, string desc, int capacity, int buyprice, int sellprice){
        this.ID = id;
        this.Name = name;
        ...
        this.buyprice = buyprice;
        this.sellprice = sellprice;
    }
    public enum ItemType {
        Consumable, Equipment, Weapon, Material
    }
    public enum ItemQuality {
        Common, Uncommon, Rare, Epic, Legendary, Artifact
}}

-- 注意:两个枚举类型ItemType和ItemQuality是在类内部声明的,在外部使用时需要通过类名,比如Item.ItemType来使用
   而且声明的时候需要为public的
-- 改进:每个Item都有自己的UI图标
  public string SpritePath { get; set; }
  并在Project中创建Resources文件夹,将所有Item图标的Sprite移入该文件夹
  其他类的构造函数里也得加上spritePath

public class Consumable : Item {
    public int HP { get; set; }
    public int MP { get; set; }

    public Consumable(int id, string name, ItemType type, ItemQuality quality, 
        string desc, int capacity, int buyprice, int sellprice, int hp, int mp) 
        : base(id, name, type, quality, desc, capacity, buyprice, sellprice) { 
        this.HP = hp;
        this.MP = mp;
}}
public class Equipment : Item {
    public int Strength { get; set; }
    public int Intelligence { get; set; }
    public int Agility { get; set; }
    public int Stamina { get; set; }
    public EquipmentType EquipType { get; set; }

    public Equipment(int id, string name, ItemType type, ItemQuality quality, 
        string desc, int capacity, int buyprice, int sellprice, int strength, 
        int intelligence, int agility, int stamina, EquipmentType equipType) :     
        base(id, name, type, quality, desc, capacity, buyprice, sellprice) {
        this.Strength = strength;
        this.Intelligence = intelligence;
        this.Agility = agility;
        this.Stamina = stamina;
        this.EquipType = equipType;
    }
    public enum EquipmentType {
        Head, Neck, Chest, Ring, Leg, Bracer, Boots, Shoulder, Belt, OffHand
}}
public class Weapon : Item {
    public int Damage { get; set; }
    public WeaponType WeapType { get; set; }

    public Weapon(int id, string name, ItemType type, ItemQuality quality, 
        string desc, int capacity, int buyprice, int sellprice, int damage, WeaponType weapType)
        : base(id, name, type, quality, desc, capacity, buyprice, sellprice) {
        this.Damage = damage;
        this.WeapType = weapType;
    }
    public enum WeaponType {
        OffHand, MainHand
}}

  -- 注意,这里因为Weapon不是继承与Equipment,因此这里使用的EquipmentType需要写成Equipment.EquipmentType

public class Material : Item {
    public Material(int id, string name, ItemType type, ItemQuality quality, 
        string desc, int capacity, int buyprice, int sellprice)
        : base(id, name, type, quality, desc, capacity, buyprice, sellprice) {
}}

-- 因为子类必须提供一个构造方法去构造父类,而父类没有空的构造方法,所以Material必须写对应的构造方法去构造父类
  否则需要在Item中写一个空的构造方法

任务7:Item类的Json文件 -- 策划

https://www.bejson.com/jsoneditoronline -- 在线Json编辑器

有很多种物品,在Json文件中保存成一个数组
  属性根据类中成员变量来确定

[
    {
        "id": 1,
        "name": "血瓶",
        "type": "Consumable",
        "quality": "Common",
        "description": "这个是用来加血的",
        "capacity": 10,
        "buyprice": 10,
        "sellprice": 5,
        "hp": 10,
        "mp": 0,
        "spritePath": "Sprites/Items/hp"
    }
]

暂时先写一个物品,用于测试

在Project->Items下保存一个记事本Items.Json文件,编码格式改为UTF-8

任务8:InventoryManager物品管理器
&& 任务14:改进Knapsack和Chest的设计

创建空物体InventoryManager,添加脚本InventoryManager.cs -- 用于管理所有物品

之后还有两个分管理器:背包Knapsack,箱子Chest
  Knapsack和Chest不是继承于InventoryManager的,只是功能结构关系而已
  背包和箱子之间有一些交互,比如移动物品等,这些交互方法就在InventoryManager中实现
  注意:InventoryManager和这些一般都为单例模式

InventoryManager.cs中

单例模式的实现
1. _instance为private,因为不能在外界访问
2. Instance为public,作为在外界访问的接口
3. 构造函数为private,不能在外界直接调用,而必须通过Instance进行调用

private static InventoryManager _instance;
public static InventoryManager Instance {
  get {
    if(_instance == null) {
      // 第一次想要得到的时候,未赋值,给它赋值
      _instance = GameObject.Find("InventoryManager").GetComponent<InventoryManager>();
    }
    return _instance;
}}

任务14:改进Knapsack和Chest的设计

因为Knapsack和Chest是有共有功能的,因此可以创建一个类Inventory作为他俩的父类

任务9&10&11:Json解析 -- LitJSON 和 JsonObject

InventoryManager需要进行Items.Json数据的解析

在Json官网 www.json.org中找到c#的 LitJSON
  或前往 https://litjson.net/
  额。。。下载失败,我直接在csdn下载了
    https://download.csdn.net/download/blackbord/10016032

下载dll文件,导入unity中就可以使用dll中的相关类了

在Project文件夹下创建Plugins文件夹,这个文件夹下的文件会被预编译,一般用于放置插件

在InventoryManager中创建解析Json文件的方法:
  ParseItemJson()

解析出来的结果为很多Item,新建一个List列表来存储
  private List<Item> itemList;

  itemList = new List<Item>();

取得Json文件的内容
  TextAsset jsonTextAsset = Resources.Load<TextAsset>("Items");
  string jsonString = jsonTextAsset.text; // 得到了文本文件中的字符串

解析
  using LitJson;
  LitJson的教程 -- https://www.cnblogs.com/Firepad-magic/p/5532650.html
  // Siki老师下载失败后,从AssetStore上import了JsonObject
  -- 会和LitJson有所区别

思路:
1. 通过API得到存储数据的对象(该对象为一个集合)
2. 通过遍历该对象,得到每一个数据对象
3. 通过"type"字段的值,判断Item的类型
4. 声明对应类型的对象,并通过构造函数新建对象
5. 将新建的对象添加到list中

LitJson版本:

// 得到的jsonData为一个集合,每一个元素也是JsonData类型
JsonData jsonData = JsonMapper.ToObject(jsonString);
foreach (JsonData data in jsonData) {
    // 将JsonData对象中存储的值,通过Item或子类的构造函数,新建一个对应的Item对象
    // 先得到共有的属性
    int id = int.Parse(data["id"].ToString());
    string name = data["name"].ToString();
    string type = data["type"].ToString();
    Item.ItemType itemType = (Item.ItemType)System.Enum.Parse(typeof(Item.ItemType), type);
    Item.ItemQuality itemQuality = (Item.ItemQuality)System.Enum.Parse(typeof(Item.ItemQuality), data["quality"].ToString());
    string description = data["description"].ToString();
    int capacity = int.Parse(data["capacity"].ToString());
    int buyprice = int.Parse(data["buyprice"].ToString());
    int sellprice = int.Parse(data["sellprice"].ToString());
    string spritePath = data["spritePath"].ToString();
    Item item = null;

    // 首先需要通过"type"的值,确认该Item是什么类型的
    switch (itemType) {
        case Item.ItemType.Consumable:
            int hp = int.Parse(data["hp"].ToString());
            int mp = int.Parse(data["mp"].ToString());
            // 通过JsonData的数据,新建一个Consumable对象
            item = new Consumable(id, name, itemType, itemQuality, description, 
                capacity, buyprice, sellprice, spritePath, hp, mp);
            break;
        case Item.ItemType.Equipment:
            break;
        case Item.ItemType.Weapon:
            break;
        case Item.ItemType.Material:
            break;
        default:
            break;
    }
    // 将新建的Item对象添加到list中
    itemList.Add(item);
}

JsonObject版本:

JsonObject讲解:readme.txt
  直接通过JSONObject的构造函数进行Json数据的解析
  得到的多个JsonObject对象会存储在list中
    事实上Json数据中的任何一个整体都是一个JsonObject类的对象
    比如一个键值对,或一个对象,或一个数组
  对于每个对象,通过jsonObject["key"]访问对应的value,根据value类型
    通过.n表示float,.b表示bool,.str表示string等等,还有Object、数组等类型

// 得到的jsonObject为一个list集合,每一个元素也是JsonObject类型
JSONObject jsonObject = new JSONObject(jsonString);
// 遍历JSONObject.list,得到每一个对象
foreach(JSONObject elem in jsonObject.list) {
    // 将对象转换为Item类
    // 通过索引器得到的为JsonObject类型
    // ToString()后发现,数据带有引号""
    // 不能使用 elementObject["name"].ToString());
    int id = (int)elem["id"].n;
    string name = elem["name"].str;
    Item.ItemType type=(Item.ItemType)System.Enum.Parse(typeof(Item.ItemType),elem["type"].str);
    ...
    Item item = null;

    switch (type) {
        case Item.ItemType.Consumable:
            int hp = (int)elem["hp"].n;
            int mp = (int)elem["mp"].n;
            item = new Consumable(id, name, type, quality, description, capacity, 
                buyprice, sellprice, spritePath, hp, mp);
            break;
        ...
        default:
            break;
    }
    itemList.Add(item);
}

任务12&13:背包的UI

所有物品的信息都保存在了InventoryManager.itemList中,
现在开发数据和UI之间的连通,将item显示在UI上

开发背包的UI

新建UI->Panel,命名KnapsackPanel,SourceImage: panel,调节颜色

屏幕自适应
  Canvas--CanvasScaler--UI Scale Mode = Scale With Screen Size
    表示按控件占屏幕的比例来显示,而不是按像素来显示
    Match = Width,表示按宽度的比例来,而高度的确定按照控件的宽高比而定

显示效果不好 -- 去掉天空盒子,Window->Lighting->Skybox选择None

新建子物体UI->Panel,命名ItemsContainer,作为所有物品的容器
  调整大小
  因为不需要显示,所以alpha=0;

新建子物体UI->Image,命名Slot,作为一个物品的容器
  SourceImage: button_square

因为需要很多个Slot,因此在ItemsContainer中添加组件Grid Layout Group,用于排序
  调整Cell大小,调整Spacing

新建Knapsack的子物体,UI->Image,命名TitleBg,SourceImage: button_long
  新建子物体, UI->Text,背包,字体等微调

因为不需要交互,取消勾选Knapsack、TitleBg、Text、ItemsContainer的Raycast Target
  只有Slot需要交互

Slot的完善:

实现鼠标移入的颜色变化效果
  在Slot上添加组件Button

制作成prefab

在Slot下创建子物体UI->Image,命名Item,作为Slot中存储的物品
  调整大小,SourceImage: 先随便选一个,因为最后是动态赋值的

  在Item下创建子物体UI->Text,命名Amount,用于显示物品数量,微调颜色等

  因为在Slot中做了交互,所以Item和Amount中的Raycast Target取消勾选

  将Item制作成prefab

给Slot添加脚本Slot.cs,用于管理自身

给Item添加脚本ItemUI.cs,用于管理Item自身的显示等功能
  因为ItemUI表示的为存在该Slot中的物体,因此需要保存该Item和数量
  public Item Item {get; set; }
  public int Amount {get; set; }

任务14~18:Inventory的实现 -- 物品存储功能
&任务19:物品存储后的UI更新显示
&任务25:Bugfixing
&任务39:添加物品时的动画显示

Inventory.cs脚本 -- 管理自身中所有的Slot
  在Knapsack中添加脚本Knapsack 继承自 Inventory

// 存储所有的Slot
private Slot[] slotList;

// 在Start()中获取所有的Slot
public virtual void Start() {
  slotList = GetComponentsInChildren<Slot>();
}
-- 因为在Knapsack等子类中也需要用到这个Start(),因此设置为virtural,方便子类访问

拾起物品并存储进背包的功能:
  public bool StoreItem(int id)
  public bool StoreItem(Item itemToStore)
  // 返回bool表示是否存储成功,因为一些原因比如背包满了

  -- InventoryManager 中根据item id返回Item对象的方法
  public Item GetItemById(int id) {
    foreach(Item item in itemList) {
      if(item.ID == id) {
        return item;
    }}
    return null;
  }

public bool StoreItem(int id) {
  // 先进行转换
  Item item = InventoryManager.Instance.GetItemById(id);
  return StoreItem(item);
}

public bool StoreItem(Item item) {
  // 安全判断
  if(item == null) { Debug.LogWarning("要存储的物品id不存在"); }

  // 存储
  // 两种情况
  // 1. 之前背包没有该类物品
    实例化一个该类物体,将其放入一个Slot
     2. 背包已有该类物品
    找到该Slot
      若物品个数小于Capacity,Amount+1 (装备等capacity为1)
      若放满了,则实例化另一个Item,并放入另一个Slot

  if(item.capacity == 1) {
    Slot slotToStore = FindEmptySlot();
    if(slotToStore == null) { Debug.LogWarning("没有空位"); return false; }
    else {  // 将物品放入该slot
      slotToStore.StoreItem(item);
  }} else {
    // 判断当前是否已经存在该类物体
    Slot slotToStore = FindSlotWithSameItemType(item);
    if(slotToStore != null) { // 找到已存在同类未满Slot
      slotToStore.StoreItem(item);
    } else { // 未找到
      // 新建一个slot存储
      slotToStore = FindEmptySlot();
      if(slotToStore == null) { ... 警告已满; return false; }
      else {
        slotToStore.StoreItem(item);
  }}}

  如何找到空格子呢?
    private Slot FindEmptySlot() 
      foreach(Slot slot in slotList) {
        if slot.transform.childCount == 0) {
          return Slot;
      }}
      return null;
    }

  如何找到类型相同的物品槽呢?
    private Slot FindSlotWithSameItemType(Item item) {
      foreach(Slot slot in slotList) {
        if(slot.transform.childCount == 1) { // 有一个子物体
          if(slot.GetItemId() == item.ID && !slot.IsSlotFilled()) {  // 符合类型且数量未满

          // ------- 在Slot中实现GetItemType()方法
          //  public Item.ItemType GetItemType() {
          //    return transfrom.GetChild(0).GetComponent<ItemUI>().Item.Type;
          //  }

          // ------- 任务25中发现:不应该判断GetItemType()
          // 这样如果血瓶和蓝瓶都是Consumable的Type,就会互相叠加了

          //  public int GetItemId() {
          //    return transfrom.GetChild(0).GetComponent<ItemUI>().Item.ID;
          //  }

          // ------- 在Slot中实现IsSlotFilled()方法
          //  public bool IsSlotFilled() {
          //    ItemUI itemUI = transform.GetChild(0).GetComponent<ItemUI>();
          //    return itemUI.Amount >= itemUI.Item.Capacity;
          //  }

            return slot;
      }}}
      return null;
    }

  如何将物品存入Slot呢?
    在Slot.cs中
    public void StoreItem(Item item) {
      if(transform.ChildCount == 0) { // 空slot
        // 实例化Item,并存入Slot
          -- public GameObject itemPrefab;
        GameObject itemObject = Instantiate(itemPrefab);
        itemObject.transform.SetParent(transform);
        itemObject.transform.localPosition = Vector3.zero;

        // 这里的Scale显示会出现Bug,在任务39中(即本节最后)会详细说明

        // 给实例化出来的空Item进行赋值
        // ---------在ItemUI中实现Item的赋值 public void SetItem(Item item, int amount = 1) {
        //  this.Item = item;
        //  this.Amout = amount;
        //  // 更新UI
        //  }

        itemObject.GetComponent<ItemUI>().SetItem(item);

      } else { // 本身已经存储了物体
        Item itemInSlot = transform.GetChild(0);
        ItemUI itemUI = itemInSlot.GetComponent<ItemUI>();
        // 这里不必判断Slot满的情况,因为在外界判断完了
        // -------- 在ItemUI中实现数量+1的方法 public void AddAmount(int num = 1) {
        //  this.Amount += num;
        //  // 更新UI
        //  }

        itemUI.AddAmount();

      }

  如何更新UI呢?

// 显示有两个部分,一个部分是Sprite,一个部分是Amount.text
private Image itemImage;
private Text amountText;

// 如果将初始化写在Start中,会报空指针,因为在一开始的时候就执行了赋值初始化
// 所以写成get的形式
public Image ItemImage {
  get{
    if(itemImage == null) {
      itemImage = GetComponent<Image>();
    }
  return itemImage;
}
public Text AmountText {
  // 相似
  amountText = GetComponentInChildren<Text>();
}

public void UpdateUISprite() {
  ItemImage.sprite = Resources.Load<Sprite>(Item.SpritePath);

public void UpdateUIText() {
  AmountText.text = Amount.ToString();

测试:

新建脚本Player.cs
  -- 因为操作物品的来源一般为Player(随意啦,一个解释而已)
  -- 通过键盘按键G,随机得到一个物品放到背包中

在 Update()中

if(Input.GetKeyDown(KeyCode.G) {
  // 随机生成一个id
  int id = Random.Range(1, 2);

  // 调用Knapsack (即Inventory中)的StoreItem(id)进行存储
  // ---------- 将Inventory做成单例模式
  // 但是不能在Inventory中实现,应该在Knapsack和Chest中实现
  // 因为如果在Inventory中实现,那么Knapsack和Chest就会共用了
  // 将Knapsack做成单例模式

//  private static Knapsack _instance;
//  public static Knapsack Instance {
//    get{
//      if(_instance == null) {
//        _instance = GameObject.Find("KnapsackPanel").GetComponent<Knapsack>();
//      }
//      return _instance;
//  }}

Knapsack.Instance.StoreItem(id);

代码:

Player.cs

public class Player : MonoBehaviour {
    void Update () {
        if(Input.GetKeyDown(KeyCode.G)) {
            // 随机生成一个id
            int id = Random.Range(1, 2);
            Knapsack.Instance.StoreItem(id);
}}}

Knapsack.cs中只有单例模式的实现代码

Inventory.cs

public class Inventory : MonoBehaviour {
    private Slot[] slotList;

    public virtual void Start () {
        slotList = GetComponentsInChildren<Slot>();
    }

    public bool StoreItem(Item itemToStore) {
        // 存储一个Item,有两种情况
        // 1. 在Inventory中没有此类Item,则寻找空Slot存储
        // 2. 在Inventory中已有此类Item
        //      若数量已满,则寻找空Slot存储;若数量未满,则增加数量即可
        // 另一种判断:
        // 1. 若Item.Capacity为1,则需要寻找空Slot存储
        // 2. 若不为1,寻找是否已经存在该类物品
        //      已存在,则数量增加;没有存在,寻找空Slot
        Slot slotToStore = FindSlotWithSameItemType(itemToStore);
        if(slotToStore == null) {
            // 没有找到相同类型且未满的Slot -- 故寻找空slot存储
            slotToStore = FindEmptySlot();
            if(slotToStore == null) {
                Debug.LogWarning("空间已满,不可进行存储");
                return false;
            } else {
                //找到空slot,进行存储
                slotToStore.StoreItem(itemToStore);
            }
        } else {
            // 找到相同类型且未满的Slot,存储
            slotToStore.StoreItem(itemToStore);
        }
        return true;
    }
    public bool StoreItem(int itemId) {
        Item itemToStore = InventoryManager.Instance.GetItemById(itemId);
        if(itemToStore == null) { // 未找到该Item
            return false;
        }
        return StoreItem(itemToStore);
    }

    private Slot FindSlotWithSameItemType(Item item) {
        foreach(Slot slot in slotList) {
            if(slot.transform.childCount == 1) {
                // 不是空slot
                if(slot.GetItemType() == item.Type && !slot.IsSlotFilled()) {
                    // 相同类型的slot,且未满
                    return slot;
        }}}
        return null;
    }
    private Slot FindEmptySlot() {
        foreach(Slot slot in slotList) {
            if(slot.transform.childCount == 0) {
                // 找到空slot
                return slot;
        }}
        return null;
}}

Slot.cs

public class Slot : MonoBehaviour {
    public GameObject itemPrefab;

    public Item.ItemType GetItemType() {
        return transform.GetChild(0).GetComponent<ItemUI>().Item.Type;
    }
    public bool IsSlotFilled() {
        ItemUI itemUI = transform.GetChild(0).GetComponent<ItemUI>();
        return itemUI.Amount >= itemUI.Item.Capacity;
    }

    public void StoreItem(Item itemToStore) {
        // 两种情况下调用该方法:
        // 1. 本Slot为空,需要实例化Item进行存储
        // 2. 本Slot不为空,只需要增加数量即可
        if(transform.childCount == 0) {
            // 实例化Item
            GameObject itemObject = GameObject.Instantiate(itemPrefab) as GameObject;
            itemObject.transform.SetParent(transform);
            itemObject.transform.localPosition = Vector3.zero;
            // 给该Item赋值
            itemObject.GetComponent<ItemUI>().SetItem(itemToStore);
        } else {
            // 数量增加
            transform.GetChild(0).GetComponent<ItemUI>().AddAmount();
}}}

ItemUI.cs

public class ItemUI : MonoBehaviour {
    public Item Item { get; set; }
    public int Amount { get; set; }
    private Text amountText;
    public Text AmountText {
        get { ... }
    }
    private Image itemImage;
    public Image ItemImage {
        get { ...  }
    }

    public void SetItem(Item item, int amount  = 1) {
        // amount默认为1,因为该方法意为被空Slot存储item时调用
        this.Item = item;
        this.Amount = amount;
        // UI更新
        UpdateUISprite();
        UpdateUIText();
    }
    public void AddAmount(int num = 1) {
        // 默认+1,因为该方法意为存储item时调用,通常存储为1个
        this.Amount += num;
        // UI更新
        UpdateUIText();
    }
    private void UpdateUIText() {
        AmountText.text = Amount.ToString();
    }
    private void UpdateUISprite() {
        ItemImage.sprite = Resources.Load<Sprite>(Item.SpritePath);
}}

任务39:添加物品时的动画显示
  -- 物品添加到Slot中时,会先放大一下物品表示强调,再缩小到应有大小

在ItemUI中控制动画的播放

流程解释:Player.Update() -> Knapsack.StoreItem(id/item) -> Slot.StoreItem(Item) -> ItemUI.SetItem(item)

ItemUI.SetItem(item)中,传递设置了item和amount,并更新了sprite和text的UI显示
  因此在ItemUI.UpdateUISprite()中
  添加
    直接在UpdateUISprite()中完成动画效果吗?
    不行,需要在Update()中不断调用Lerp来实现

定义属性
  private float targetScale = 1;

Update() {
  if(Mathf.Abs(transform.localScale.x - targetScale) > 0.05f) {
    // 进行动画播放
    transform.localScale = Vector3.one * Mathf.Lerp(transform.localScale.x, targetScale, Time.deltaTime*smooth);
  } else {
    transform.localScale = Vector3.one * targetScale;  // 节约性能
    if(targetScale != 1) {
      // 每当添加物品时,会将targetScale设大,播放动画
      // 结束动画后localScale=targetScale>1,此时自动将targetScale设为1,开始变小动画
      targetScale = 1;
}}}

Bug修复:在Slot.cs的StoreItem()里有一个scale自动变化的问题

public void StoreItem(Item itemToStore) {
    if(transform.childCount == 0) {
        // 实例化Item
        GameObject itemObject = GameObject.Instantiate(itemPrefab) as GameObject;
        // 大小显示一直有问题,在这里手动设置
        SIKI_Unity_2_初级案例_贪吃蛇

SIKI_Unity_2_入门_通过实例学习游戏的存档和读档

SIKI_UNITY_灯光与渲染

Siki_Unity_1_Unity零基础入门_打砖块

Siki_Unity_3-7_AssetBundle从入门到掌握

动态规划_01背包_完全背包_多重背包_分组背包