《麦田物语》项目学习笔记

《麦田物语》学习博客

本文旨在记录我在学习《麦田物语》这个项目时,觉得有趣且有用的知识点,希望以后的我不要忘了下面这些知识哈哈

  1. 不需要的资源文件删除,可以提高整个项目的流畅度

  2. 可以保存图片设置的预设,方便执行多图片重复的设置配置

  3. 将不会更改的场景部分(玩家,摄像机,各种管理者)单独设置成一个场景,而更改的场景部分就是地图场景。而切换地图的原理就:将所有场景添加至一起,将不需要的地图场景卸载,只保留需要的地图场景(而不是原来的使用SceneManager来直接Load)

  4. 可以更改FocusMode来单独观察特定的图层,防止瓦片地图绘制到错误的图层上

  5. 可以用unity自带的 随机笔刷 来绘制随机瓦片

  6. 如果是像素游戏,则可以在相机上添加 Pixel Perfect 来使得像素更加清晰。然后可以在相机上设置 DeadZoom (默认不设置) 来使得相机出现缓冲,移动更加自然

  7. 可以在 SpriteEditor 中设置图片的默认物理碰撞。(如果是需要影响之前的物理碰撞,则关闭游戏对象在启动游戏对象就可以了)

  8. 可以专门设置一个地图瓦片的碰撞体图层来专门设置瓦片的碰撞体,这样子能更加直观的设置碰撞体和节省性能。为了不显示碰撞体图层,故将碰撞体图层的 SpriteRender关闭就可以了。

  9. 如果不清楚一个组件的使用方式,可以点击改组件右上角的 " ? " 来快速进入到unity的官方手册中

  10. (个体案例辐射全局)在边界碰撞体 手动用代码 控制切换碰撞体时,需要手动清理其缓冲,防止出现边界切换了,但又没完全切换的情况

  11. 💡💡💡可以使用属性**[RequireComponent(typeof(组件名字))]**来使得脚本挂载的对象上一定需要特定的组件(防止滥用)

  12. 可以使用DoTween来代替协程来实现各类渐变效果(以前是使用动画器来控制来着,是比较耗性能)

    • 首先需要using DG.Tweening

      	private SpriteRenderer SR;
      	private void FadeOut() {
              SR.DOColor(new Color(1, 1, 1, 0), 0.35f);
          }
      
          private void FadeIn() {
              SR.DOColor(new Color(1, 1, 1, 1), 0.35f);
          }
      

      0.35指的是变化时间,实在是方便

  13. 使用Dotween会有部分无法使用public的变量调节其中的参数or时间,故这里可以用一个工具类Setting来装各种全局变量or常量来配置,这里就是所谓的Config

    • public class Settings {
          public const float fadeDuration = 0.35f;
      }
      // 使用
      // spriteRenderer.DOColor(targetColor, Settings.fadeDuration);
      // 好像在C#中的const常量就是默认static变量来着,故不可以用 static const int a 这种说法
      
  14. 在写好的方法前面写上 三个/ (///)就可以注释方法,这样在调用方法的时候就可以看见方法是干什么的

  15. 可以专门用一个脚本来集中写自己所DIY的类or结构体,来集中管理

  16. 可以使用 枚举 来管理各种类型相关的东西(比如物品类型,就可以用一个枚举来管理)。然后所有的枚举可以写在一个枚举脚本中集中管理

  17. 💡💡💡为了使得自己写的类和结构体被 unity 识别,要在类前面加上[System.Serializable]使其序列化

  18. 可以使用MVC的方式编写整体框架,其意思就是 把数据控制和显示 分开

  19. (个体案例辐射全局)💡💡💡💡💡可以用一个Data SO 中加载了一个列表来作为 整个游戏的物品管理数据库。倘若想添加物品,则直接在SO的列表上添加物品就可以了,不用反复一直创建单个物品的SO

  20. 可以使用 UI ToolKit 创建一个编辑器(比如物品编辑器)来编辑具体的物品,提高工作效率

  21. 关于 UI ToolKit 的使用技巧

    1. 可以勾选视框中的 Reorderable (可重新排序) 以启用排序功能
    2. 勾选 Show Add Remove Footer 来添加一个脚注来更方便的进行元素的删减添加
    3. 勾选 Show Border 来显示UI边界
    4. 可以点 Library 右侧的三个点,来打开编辑器特有功能,便可使用很多编辑器特有功能
    5. 如果要给编辑器输入一个图片,则 使用 Object Field ,然后在 Type 中输入 UnityEngine.Sprite 便可以输入一个图片精灵了
    6. 可以勾选 Multiline 来多行显示
    7. 若要输入bool 值,则可使用 Toggle 控件
    8. 可以在 Size 选项中使用百分比来控制大小,防止缩放有问题
    9. 为了使得 ListView 能一直延伸下去,可以将 Flex -> Grow 改成 1 便可
  22. 超级解耦合:不需要使用 public 的拖拽 来实现SO的传输,可以使用 AssetDatabase.FindAssets 以查找的方式获取SO

    • 这个函数返回的是一个string[],且括号内要求的是一个带格式的字符串,格式为:"t:数据类型"(注意:如果加了 t 则会报错,所以这里需删了 t 。但是使用案例又有 t ,所以总结是unity 奇怪的bug)

    • 这个函数返回的string是查找文件的GUID,需要根据GUID来获取文件的路径,从而获取该文件。GUID转换成路径需要使用**AssetDatabase.GUIDToAssetPath。路径查找文件资源需要使用AssetDatabase.LoadAssetAtPath**,注意这个函数是返回一个Object而不会直接返回SO文件,所以这里要使用一个转换,可以直接加括号,但这里更推荐使用C#自带的 **A as B**更加优美

    • 最终还需要将读取后的数据标记保存,否则无法保存数据。需使用EditorUtility.SetDirty(读取的数据)

    • ItemDataList_SO dataBase;
      // 此处是想要获取类型是 ItemDataList_SO 的SO文件
      string[] dataArray = AssetDatabase.FindAssets("ItemDataList_SO");
      
      if (dataArray.Length > 0) {
          // 获取路径,需要传入一个GUID
          var path = AssetDatabase.GUIDToAssetPath(dataArray[0]);
          // 读取数据,需要传入路径和查找的类型,当然还得手动转换一下
          dataBase = AssetDatabase.LoadAssetAtPath(path, typeof(ItemDataList_SO)) as ItemDataList_SO;
      }
      
      // 如果不标记则无法保存数据💡
      EditorUtility.SetDirty(dataBase);
      
  23. 单例模板

private void Awake() {
        if (instance == null) {
            instance = this;
        } else if (instance != this) {
            Destroy(gameObject);
        }
    }
  1. 可以使用命名空间来避免 单例乱调用,耦合度提高的情况。(使用单例,则需要using对应的命名空间才可使用)

  2. 在2D平面中若要实现正确的遮挡关系

  • Sprite排序点 设置为 轴心
  • 设置好对应的排序图层
  1. 新背包系统的好处

    1. 不需要手动传SO了,只需要输入物品的ID即可查找到对应的物品信息,效率高。且图片也不需要自己一个个手动查找添加,好生偷懒。(但是为了编辑方便,结果还是需要手动添加…)

    2. 玩家的背包系统只需要 存储物品的 ID 和 数量 就可以了,不需要存储别的东西。故这里使用结构体来作为背包物品单位。且在检查背包空位时,只要背包位置上的ID为0,则说明空着物体,更加方便。

  2. (相当好的优化方法)把背包拾取的 Trigger 检测 安排到player身上,避免了多物品出现的时候多个Trigger判定 来占用不必要的内存。

  3. C#初始化的小知识:甚至可以直接用列表初始化,十分方便(这下连构造函数都可以不用写了)

    var item = new InventoryItem { itemID = 1001, itemAmount = 1 };
    
  4. 💡C# 结构体小知识:C#中无法修改结构体的成员!如果要达成修改效果,只能用一个新的结构体来覆盖旧的结构体来实现修改效果!

    • C# 中只有类可以修改其成员
  5. 写UI的时候要 单独创建一个场景存放UI (实际工作场合也是需要在单独场景存放UI的)
    • 编写UI的技巧
      1. 在创建新的画布时,更改 Canvas Scaler中的 UI Scale Mode ,修改成Scale With Screen Size 中的 1920 * 1080
        • 如果是像素游戏,这里还要改下面的 每单位多少像素
      2. 在需要按钮or图标工整排序时,就可以使用 Layout Group这个组件来排序
        • 也可以使用 Layout Element组件来实现在排序组中标新立异的格子,比如在组中的一个格子单独不受排序的影响等等来忽略布局的影响(用于实现物品栏旁边的背包or各种状态栏)
        • 在使用Grid Layout Group 时,可以直接设定每一个格子的大小!!!
      3. 把UI栏中单独的格子(比如各种道具格子,装备格子)设置成预制件,就可以实现牵一发而动全身
      4. 部分图片UI拉伸后并不美观,可以在 Image 中设置图片类型为 sliced ,然后在精灵编辑器中设置拉伸的范围,便可以实现部分(纯色部分)拉伸,而精致部分不会拉伸的效果了
      5. 如果不想让按钮 or 进度条(做血条的时候)可以有互动效果,则可以勾选取消掉 Interactable (在做背包空闲格子的时候不需要互动,做血条的时候不需要进度条拖拽功能)
      6. 如果不想让 按钮 or 进度条等 和键盘有互动(用键盘切换格子,调节进度条),则可将 Navigation 调整至 none
  6. (有别于SO + 事件解耦合)静态类 + 事件解耦合💡💡💡💡💡

    • 专门写一个静态类(偷懒命名:EventHandler),然后里面全都是静态的事件供他人订阅。这种解耦合的方法似乎更大众化一点,而且比 SO + 事件 方便好写多了

    • 事件应当在OnEnable时订阅,在OnDisable时取消订阅

    • 可以在静态类中写好调用的函数,使得代码更加简洁,可读性更高

      public static Action<InventoryLocation, List<InventoryItem>> UpdateInventoryUI; 
      
      // 命名直接就是 Call + 事件名字
      public static void CallUpdateInventoryUI(InventoryLocation location, List<InventoryItem> list)      
      {          
          UpdateInventoryUI?.Invoke(location, list);      
      } 
      
    • 订阅函数的命名(方便为主):On + 事件名字。通常以私有为主

  7. 如果只是需要拖拽获取控件,则可使用 [SerializeField] private XXXXX 的形式去写,而不是使用 public

  8. 不用 active 来获取物件的活跃状态的bool值,而是使用 activeInHierarchy 来获取

    • 设置状态也不要使用XXX.active = false,而是使用 XXX.SetActive(false)
  9. 启用点按事件(利用Unity自带的事件和接口来实现)(仅对UI有效!!!)

    1. 使用using UnityEngine.EventSystems

    2. 使得控件类 继承 自带接口 IPointClickHandler

      • public class SlotUI : MonoBehaviour, IPointClickHandler { }
    3. 可以右键让编译器补全,实现这个接口,也可以自己手动写一个 OnIPointClickHandler(PointerEventData eventData) {}的函数实现这个接口。最后想实现的东西就直接在这个回调函数中实现就可以了

  10. 启用拖拽事件(与上同)

    1. 使用using UnityEngine.EventSystems
    2. 使得控件类 继承 自带接口 IBeginDragHandlerIDragHandlerIEndDragHandler (甚至要三个接口,分别是:开始拖拽,拖拽过程中,结束拖拽)
      • public class SlotUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { }
    3. 可以右键让编译器补全,实现这个接口(推荐)
  11. 拖拽的实现

    • 原理:新创建一个画布,上面添加一个和背包格子大小一致的图片,当拖拽物体时,将该图片移动到格子处且显示对应的图片,松开拖拽时就取消图片的显示。整体是使用了一个障眼法,而不是真的将这个格子移动!(做游戏特有障眼法)

    • 初始配置

      1. 在主Canvas下创建一个新的Canvas
      2. 关闭Canvas控件中的 Pixel Perfect
      3. 勾选Canvas控件中的Override Sorting(希望排序)
      4. 调高Sort Order 至 2 ~ 3
      5. 删除控件Canvas Scaler(因为主Canvas已经有了)
      6. 💡💡💡在新的Canvas下新添加的Image物体上,取消其 Raycast Target (是否会被其他图片遮盖,这里是拖拽所以完全不需要)
    • 注意事项

      1. 在写开始拖拽的时候,建议写一个SetNativeSize()来初始化图片原始大小来防止图片失真
      2. 💡💡💡可以使用eventData.pointerCurrentRaycast.gameObject来获取拖拽结束位置,射线所获取到的物体,相当有用了
      3. 💡💡💡这类单图片的物体的显示就不用改变游戏对象的活跃了,而是enable or disable该物体下的图片控件便可控制其显示
      4. 💡💡💡💡💡当拖拽到对应格子上时,射线获取的可能是格子上的高光物体or显示数字的文本物体,这个时候就需要取消对应的 Raycast Target 来避免射线的错误获取物体(找不到的可能在Extra Setting处)
    • 相当有用的举一反三的知识

      1. 拖拽事件的eventData可以返回拖拽获取到的游戏对象,可以利用这个实现很多有意思的东西(比如打牌or把一个道具拖拽到一个具体的物体上可以有特殊用途)

        • eventData.pointerCurrentRaycast.gameObject
          
      2. 💡💡💡将鼠标坐标转换为可用的世界坐标

        • // 鼠标对应世界地图坐标
          // 新操作系统
          var pos = Camera.main.ScreenToWorldPoint(new Vector3(Mouse.current.position.x.ReadValue(), Mouse.current.position.y.ReadValue(), -Camera.main.transform.position.z));
          
  12. 鼠标指针划入和划出时的事件(unity自带事件)

    • 使用**IPointerEnterHandlerIPointerExitHandler**
    • 生成这个函数的接口,实现他,便可
  13. 可以直接用属性的写法直接获取组件,暂且不清楚这种初始化的优先级是否和Awake一致,安全性存疑

    • private InventoryUI inventoryUI => GetComponentInParent<InventoryUI>();
  14. 可以用 Content Size Fitter 来使得UI由内容而扩容

  15. 想要获取TMP文本,则需使用using TMPro,其变量名为**TextMeshProUGUI**(常忘)

  16. C#崭新的关于Switch的语法糖💡

    • private string GetItemType(ItemType itemType) {
          return itemType switch {
      		ItemType.Seed => "种子",
      		ItemType.Commodity => "商品",
      		_ => "默认无"
          }
      }
      

      利用这个来快速赋值。其中原switch中的default在这里的语法糖中 以下划线为替代!!!!

  17. 因为计算坐标是从锚点开始计算的,所以如果是UITip这类希望UI始终朝上的话,需要将锚点置于底部

  18. 关于更换装备动画以及更换各种持有物品动画,需使用 Animator Override Controller

  19. 可以使用控件 Mask 来实现血条 or 其他的遮挡UI效果(时间切换)

    • 父物体限定显示范围并添加上控件Mask,子物体显示填充内容
  20. C#小知识

    • 可以使用如num.ToString("00"),里面添"00"来确保是双位显示
  21. 关于加载场景

    1. 异步加载场景需使用 SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive)。这个东西是异步加载,所以需要放在协程中进行,即

      private IEnumerator LoadSceneSetActive(string sceneName)
              {
                  yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive);
      
                  Scene newScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
      
                  SceneManager.SetActiveScene(newScene);
              }
      
    2. SceneManager.LoadSceneAsync的第二个参数是直接将场景叠加在上一个场景上,而不是直接切换,合适这个项目的场景切换

  22. 默认Panel的图片的角落是有圆角的,如果不想要圆角的话则将图片选择成Square便可

  23. 实现场景淡入淡出,可以使用控件Canvas Group,然后修改其中的Alpha值便可。这边注意如果是仅仅实现淡入淡出的效果,则应当把Canvas Group中的Blocks Raycasts给取消,这样使得不会遮挡鼠标的点击

  24. 💡💡💡如果是判断两个float值是否相等,由于float值小数点很多,单单的==可能会有bug,所以这里需使用Mathf.Approximately(A, B)来“近似等于”判断这两个数

  25. 💡💡💡让一个数缓慢以一个速度变化到另一个数,可以使用Mathf.MoveTowards(当前值,目标值,速度 * Time.deltaTime),可以用来做透明度变化(搭配携程)

    • 可以用来做渐变效果,配合透明度
  26. 如果在跨场景用Find来获取控件时,应当将语句写在Start中,如果写在Awake里,会导致Find的时候,对应的控件还没有生成

    • 拖拽的方式是无法跨场景的!!!
  27. C#语法糖

    • C#的类的初始化甚至可以这样,纯粹的语法糖,真好吃

      foreach (var item in items) {
          SceneItem sceneItem = new SceneItem {
              itemID = item.itemID, itemNum = item.itemNum
          };
      }
      
  28. C#字典的语法糖

    1. Dict.ContainsKey(查找的key)来直接查找key,而不像C++一样明明也是找key,但是名字还是叫Find

    2. 特别好用的Dict.TryGetValue(想要获取的value所对应的key, out 如果找到了,则将value赋值给这个参数),切记第二个参数前要加一个out。且注意这里的value可以为空

      int a = 0;
      // 如果找到值,则直接赋值给a
      dict.TryGetValue("1", out a);
      
  29. 虚拟鼠标的做法:又是用一个图片跟随鼠标位置来达成障眼法

  30. 如何判断鼠标和游戏对象重叠(用于切换鼠标图片)

    • 可以直接使用unity自带事件,且不是前面的接口的用法,而是更简单的用法
      • 💡💡💡EventSystem.current.IsPointerOverGameObject()来判断鼠标是否与对象重叠,简简单单
  31. 如果需要让代码在编辑模式下进行

    • 在全部类上加入一个属性[ExecuteInEditMode]

    • 判断该脚本是否在游戏运行状态运行

      if (Application.IsPlaying(this)) {XXX}
      
    • 用宏 去书写 编辑模式特有代码

      #if UNITY_EDITOR
                  if (mapData != null)
                      EditorUtility.SetDirty(mapData);
      #endif
      
    • 当在非游戏状态下用代码修改SO 时,使用EditorUtility.SetDirty(SO)可以做到保存当前修改的作用,类似于 Ctrl + s(需注意要using UnityEditor

  32. 对于单个瓦片做鼠标射线判断的时候,就需要将鼠标从 屏幕坐标 转换为 世界坐标 再转换为 网格坐标,才能对网格做出判断

  • 做出屏幕坐标转换世界坐标时,需要获取到相机Camera,而这里并不需要GetComponent,而是

    private Camera mainCamera;
    private void Start() {
        mainCamera = Camera.main;
    }
    

    便可获取主摄像机。注意这里需要将主摄像机的标签设置为MainCamera

  1. 转换世界坐标需要一个camera作为参照,转换网格坐标需要一个grid作为参照
// 屏幕坐标转换为世界坐标
        mouseWorldPos = mainCamera.ScreenToWorldPoint(Mouse.current.position.ReadValue());
// 世界坐标转换为网格坐标
        mouseGridPos = currentGrid.WorldToCell(mouseWorldPos);
  1. 当需要根据很多详细信息去寻找一个物体(物品细节or属性)的时候,如要根据 坐标,所在场景,甚至名字,然后要根据这些具体信息返回。则可以使用 字符串的形式 ("X1Y2Scene1"这种字符串就包含了上面的所有信息) 串联起全部信息,然后利用一个字典来根据这些信息返回具体的物品细节or属性

    • 这可以存储查找各种瓦片地块,或者卡片游戏里的卡片
  2. 💡💡💡💡💡考虑使用距离的平方,而不是距离

    • float distanceSqrd = (transform.position –
      other.transform.position).sqrMagnitude;
      
      if (distanceSqrd < (targetDistance * targetDistance)) {
      // do stuff
      }
      
  3. 🍅🍅🍅使用Vector3.MoveTowards的正确方法

    • transform.position = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);注意这里速度还要 * Time.deltaTime

    • Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);不是直接摆一个函数就可以移动坐标了的!!!!

  4. 💡💡💡💡💡为了不让命名冲突,以后写东西都写在自己的命名空间里!!!(之前的项目就有过"Mouse"的命名冲突!)

  5. 种地更改瓦片(终于到了种地环节)

    • 使用Tilemap.SetTile(Vector3Int pos, TileMaps.TileBase tile)来实现更换瓦片
      • 这里的更换瓦片甚至可以是RuleTile,帮大忙了
      • 记得 using UnityEngine.Tilemaps
    • 设置规则瓦片的时候不要只设置九宫格的规则,还应当设置单行和单列的规则瓦片的规则!!!
    • 利用 搜索标签的方式 去获得当前的瓦片地图(因为场景会切换,所以当前最好的方式就是直接FindWithTag)(可以放在切换场景的事件中去调用,去寻得当前瓦片地图)
    • 关于52人物动作的课程 先跳过,若有需求,则再细看(因为主要想用状态机实现,还是看需求吧)
      • 会使用Animator Override Controller
    • 保存地图数据(场景反复切换后仍保存)
      1. 记得总的地图的信息都是保存在字典里的,所以要修改字典
      2. 每次加载场景要根据字典
    • 若想要根据时间而变化地图的细节,则直接写一个比如 一天过去后 的事件来调用就可以了
  6. 💡💡💡C#语法糖:C#的字符串可用直接用str.Contains("123")来查找"123"有没有出现在str中,十分强大

  7. C# 知识 :当C#里没有迭代器这种好东西,如何遍历字典这类数据结构呢?

    • 其中一个最懒的方法就是使用foreach

      foreach (var entry in sample_Dict)
                  System.Console.WriteLine(entry.Key + ":" + entry.Value);
      

      foreach得到的每一个元素其实是一个 键值对,需使用键值对.key 键值对.Value的方式来访问

  8. C# 知识 or 语法糖 :对于一个固定规则获取的变量,对其初始化可以这么写

    • // 记录一个总数
      public	int totalGrowthDays {
          get {
              int amount = 0;
              for (int i = 0; i < growthDays.Count; ++i) {
                  amount += growthDays[i];
              } 
              return amount;
          }
      }
      

      语法糖实在是太甜了,简单方便又快捷,代码优美的不行

  9. 💡💡💡如果[Header("XXX")]放太多了,想要来一点层次感,则可以用**[Space]**直接创造出一个空行,来营造层次感(绝了,又学到一个好东西)

  10. 💡C#又一语法糖,通过lambda表达式来限制 列表查找 的条件

    public ItemDetails FindItemByID(int ID) {
                return itemDataList.itemDetailsList.Find(i => i.itemID == ID);
    }
    
  11. 💡💡💡通过通过物理方法,获取鼠标当前位置的碰撞体数组(项目里用于收获植株)(不一定是鼠标,这里是获取一个点上的所有碰撞体数据,然后进而去更改其他脚本)

    Collider2D[] cds = Physics2D.OverlapPointAll(mouseWorldPos);
    
  12. 新输入系统的用法Get

    • 可用Key来设置一个专门的键位

      public Key key
      
    • // 便可用这种方式实现自定义快捷键!!!!
      Keyboard.current[key].wasPressedThisFrame
          
      /* 
      Keyboard.current[key.A].wasPressedThisFrame 
      等同于 
      Keyboard.current.KeyA.wasPressedThisFrame
      
  13. 粒子系统tip
    1. 发射器速度模式 上,如果不是模拟刚体的话,就使用 transform模式(对坐标的计算)(翻译做变换)就好了,节省资源

    2. 可以调整 生命周期内的颜色 来达成粒子效果在各个阶段具有不同颜色和透明度的特点,可以做的非常漂亮

    3. 可以调整 生命周期内的旋转 来达成旋转

    4. 可以调整 纹理表格动画 来替换粒子效果的图片

    5. 可以调整 噪音(noise,噪点,什么奇怪翻译) 来调整粒子的晃动效果,可以以此实现落叶的晃动飘落效果,美感++

    6. 如何管理粒子系统:使用 枚举enum + 对象池

    7. enum一个粒子类型,用于管理

    8. 可以在 渲染器 中调整粒子特效的图层,使得不会被遮盖

  14. 对象池的使用

    1. `using UnityEngine.Pool`
    2. 比如这里要管理粒子
    

    默认参数👇[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WqDFTQoU-1685451275079)(D:_我的MarkDown笔记\图片存放\image-20230313213749130.png)]

    参数皆为委托,为各个阶段执行的操作

    // 正常的用法可以直接使用gameobject就可以了
    // public ObjectPool<GameObject> pool 就已经可以管理绝大部分的对象了
     var newPool = new ObjectPool<GameObject>(
                    () => Instantiate(item, parent),
                    e => { e.SetActive(true); },
                    e => { e.SetActive(false); },
                    e => { Destroy(e); }
                );
    

    利用Get(), Realease()来操作

    private void OnParticleEffectEvent(ParticaleEffectType type, Vector3 pos) {
            // TODO:根据特效去补全
            // 这里要求每一个类型和数组下标一一对应
            ObjectPool<GameObject> objPool = type switch {
                ParticaleEffectType.落叶粒子 => poolObjectList[0],
                _ => null,
            };
            // 从池中拿对象并放置
            GameObject obj = objPool.Get();	// 💡💡💡
            obj.transform.position = pos;
            // 利用携程进行回收
            StartCoroutine(ReleaseRoutine(objPool, obj));
        }
    
        // 以携程的形式进行对象的回收
        private IEnumerator ReleaseRoutine(ObjectPool<GameObject> pool, GameObject obj) {
            yield return new WaitForSeconds(1.5f);
            pool.Release(obj);				// 💡💡💡
        }
    
  15. 可直接使用?.直接保证函数实施者的非空,例如currentCrop?.BeTheOne()

  16. AStar 算法

    1. A*的节点比较需要用到 using System 中的接口 IComparable

    2. 数据结构

      public class AStarNode : IComparable<AStarNode> {
              [Header("节点网格坐标")]
              public Vector2Int gridPos;
      
              [Header("距离开始格子的距离")]
              public int gCost = 0;
      
              [Header("距离目标格子的距离")]
              public int hCost = 0;
      
              // 当前格子的权重
              public int FCost => gCost + hCost;
      
              // 当前格子上是否有障碍
              public bool isObstacle = false;
      
              // 父节点
              public AStarNode ParentNode;
      
              // 构造函数
              public AStarNode(Vector2Int pos) {
                  gridPos = pos;
                  ParentNode = null;
              }
      
              // 用于节点间比较的接口  大于:1   等于:0   小于:-1
              // 这里有点递归的成分在里面了
              public int CompareTo(AStarNode other) {
                  // 先比较两个节点的FCost
                  int result = FCost.CompareTo(other.FCost);
                  if (result == 0) {  // 如果两个节点的FCost相同,则选择距离目标节点更近的节点
                      result = hCost.CompareTo(other.hCost);
                  }
                  return result;
              }
          }
      
  17. 更新点 C#小知识

    1. HashSet(其实就是C++里的Set),目的是使用查找函数Contain()的时候,HashSet的速度远快于List

    2. SortSet(这个和C++原生Set一样会排序,C#的原生Set不会排序!!!)

    3. 当在C#中,一个函数想返回两个值的时候,就可以利用小特性,在参数中添加 out 的前缀,表明是要返回的值(不用像C++一样要整一个结构体了)(可直接写在函数形参上)

    4. 遍历栈也可以直接 foreach 来直接遍历,例如foreach (var step in npcMovementStepStack) {}

    5. // 顶级语法糖,这个TryPop直接把判断非空给省了,完爆Pop
      if (dailogueStack.TryPop(out DialogPiece result)) {}
      
    6. C#中的字符串转换成整型的方法

      •  int amount = Convert.ToInt32(tradeAmount.text);
        
  18. 当在写时间戳的时候,可以利用 C# 自带的 时间封装单位(甚至C#自带),主要用处是 用来装各种时间单位,作为一个总体的时间来使用

    • using System

    • public TimeSpan GameTime => new TimeSpan(gameHour, gameMinute, gameSecond);	// 只是其中一种用法
      
  19. 协程小知识[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNJWyduE-1685451275081)(D:_我的MarkDown笔记\图片存放\image-20230322194048259.png)]

  20. 基于麦田物语的制作场景时的小tips

    • 创建 MapData 添加到 Properties 当中并设置好 origin 和 size 还有场景名字
    • MapData 数据需要添加到 GridMapManager 的数据列表当中
    • 绘制地图也要绘制 Collision|NPCObstacle
    • 添加 Bounds 摄像机边界 别忘记 Tag
    • 创建 ItemParent 和 CropParent 别忘记 Tag
    • Teleport 要设置好目标场景和目标坐标
    • 创建好了场景别忘记加到 Build Settings 当中
    • 设置好 SceneRoute 路径点
  21. 可使用 [TextArea] 直接给字符串创建一大片输入区域,方便输入

  22. UnityEvent的使用

    • using UnityEngine.Events
    • 直接创建一个 public UnityEvent OnFinishEvent。就会得到一个类似于按钮的回调函数事件的一个窗口
    • 这类自制回调函数可用来调用各种窗口,开启各种音乐,CG,非常好用了
  23. (DoTween)如果是Text类型,可以直接用 DoText() 来实现打字效果,但却不支持 TMP,但可以用 DoTween通用方法实现

    • 通用方法:DOTween.To( getter, setter, endValue, float duration ),前两个参数是函数指针,用lambda会好用点

    • 参数解释

      1. 你要对哪一个目标进行调整

      2. 你要对这个目标做什么操作,以让他达到变化的目的(一般直接等式就好)

      3. 最终的要变化成的目标值

      4. 这个过程所用的时间

    • DOTween.To(() => currentLight.color,
                 value => currentLight.color = value, 
                 targetLight.color,
                 3f).SetEase(Ease.Linear);
      
      
      /*public TextMeshProUGUI tmp;
       
          void Start()
          {
              string text = tmp.text;
              DOTween.To(() => string.Empty, value => tmp.text = value, text, 3f).SetEase(Ease.Linear);
          }
      */
      
    • [static DOTween.To(getter, setter, to, float duration)](javascript:void(0))关于这一段的官方手册

    • 这个方法是通用方法,所以不仅仅是TMP,甚至Vector3都可以,就很强大

  24. 一个用代码手动添加 按钮点击的回调函数 的方法,这种方法主要好处是可以添加 private 的函数,增强保密性

    // 手动添加 按钮点击的回调函数
    cancelBtn.onClick.AddListener(CancelTrade);
    
    private void CancelTrade() {
        gameObject.SetActive(false);
    }
    
  25. 如果一个实体在测试窗口能看见,但在Game窗口下看不见,则可能是 摄影机 的 z轴 和 实体的 z轴间的问题(摄影机拍不到实体,故导致)

    • 原因:在生成实体时,用的是鼠标的坐标去生成,而摄影机默认z坐标是 -10 ,而鼠标的坐标的默认z坐标也是 -10,就导致摄影机拍不到物体(平行了)
    • 解决方法:在生成实体的时候,修改实体的z坐标大于 -10 便可
  26. 如果想让 初始化 在切换场景时启用,可以写在OnEnable中,因为 OnEnable 在拆卸装载场景时都会调用,而Start只会调用一次

  27. 关于升级URP

    • 包管理器里搜 Universal RP
    • 在 项目设置 里,把材质部分选用URP的渲染管线
    • 升级大体流程:
      • 升级 URP 的材质的时候,请加载所有在 Hierarchy 中的场景,然后再点击 Convert Asset
      • window -> Rendering -> Render Pipeline Converter
      • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-NhMkHQGX-1685451275081)(D:_我的MarkDown笔记\图片存放\image-20230403163207573.png)]转换就完事了,如果有报错,直接继续点就可跳过报错接着运行了
    • 升级完后,粒子特效变成紫块。此时需要把粒子特效的渲染材质改一下[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c71jmGWl-1685451275082)(D:_我的MarkDown笔记\图片存放\image-20230403163616065.png)]改成这个就可以了
  28. 关于URP的使用

    • using UnityEngine.Rendering.Universal
    • 一般使用 全局光源 来做昼夜变化的总光源
    • 用一个数据类型来记录单个节点的灯光,然后用一个列表来收集这些数据。这种方法十分常用[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDLL8whD-1685451275082)(D:_我的MarkDown笔记\图片存放\image-20230403170319811.png)]
  29. 可用 [Range(0f, 1f)] 前缀来使用滑动条来控制数值

  30. Audio Mixer 中的各个元素的音量大小是需要 手动右键 暴露出来的,这样才能进行代码上的修改

  31. Timeline

    • using UnityEngine.Playables
    • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RB3vLDi8-1685451275083)(D:_我的MarkDown笔记\图片存放\image-20230411191530611.png)]用于自定义timeline的行为
  32. 可使用这种方式[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhMVTAI5-1685451275084)(D:_我的MarkDown笔记\图片存放\image-20230411212040772.png)]使得某个对象处于最下层,得到优先渲染,实现类似 叠层 的效果

  33. 健忘点:Application.Quit();用于退出游戏,别老是忘记

  34. 有关存档

    • 为了存储 列表 和 字典 的数据,故使用新的工具
      • 在包管理器中 直接 add for URL ,输com.unity.nuget.newtonsoft-json便可
  35. 对于多场景叠加的设计模式

    • 在打包的时候,是只加载一个场景的,所以像UI之类的场景需要手动添加进去
  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值