《麦田物语》学习博客
本文旨在记录我在学习《麦田物语》这个项目时,觉得有趣且有用的知识点,希望以后的我不要忘了下面这些知识哈哈
-
把不需要的资源文件删除,可以提高整个项目的流畅度
-
可以保存图片设置的预设,方便执行多图片重复的设置配置
-
将不会更改的场景部分(玩家,摄像机,各种管理者)单独设置成一个场景,而更改的场景部分就是地图场景。而切换地图的原理就:将所有场景添加至一起,将不需要的地图场景卸载,只保留需要的地图场景(而不是原来的使用SceneManager来直接Load)
-
可以更改FocusMode来单独观察特定的图层,防止瓦片地图绘制到错误的图层上
-
可以用unity自带的 随机笔刷 来绘制随机瓦片
-
如果是像素游戏,则可以在相机上添加 Pixel Perfect 来使得像素更加清晰。然后可以在相机上设置 DeadZoom (默认不设置) 来使得相机出现缓冲,移动更加自然
-
可以在 SpriteEditor 中设置图片的默认物理碰撞。(如果是需要影响之前的物理碰撞,则关闭游戏对象在启动游戏对象就可以了)
-
可以专门设置一个地图瓦片的碰撞体图层来专门设置瓦片的碰撞体,这样子能更加直观的设置碰撞体和节省性能。为了不显示碰撞体图层,故将碰撞体图层的 SpriteRender关闭就可以了。
-
如果不清楚一个组件的使用方式,可以点击改组件右上角的 " ? " 来快速进入到unity的官方手册中
-
(个体案例辐射全局)在边界碰撞体 手动用代码 控制切换碰撞体时,需要手动清理其缓冲,防止出现边界切换了,但又没完全切换的情况
-
💡💡💡可以使用属性**[RequireComponent(typeof(组件名字))]**来使得脚本挂载的对象上一定需要特定的组件(防止滥用)
-
可以使用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指的是变化时间,实在是方便
-
-
使用
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 这种说法
-
-
在写好的方法前面写上 三个/ (///)就可以注释方法,这样在调用方法的时候就可以看见方法是干什么的
-
可以专门用一个脚本来集中写自己所DIY的类or结构体,来集中管理
-
可以使用 枚举 来管理各种类型相关的东西(比如物品类型,就可以用一个枚举来管理)。然后所有的枚举可以写在一个枚举脚本中集中管理
-
💡💡💡为了使得自己写的类和结构体被 unity 识别,要在类前面加上
[System.Serializable]
使其序列化 -
可以使用MVC的方式编写整体框架,其意思就是 把数据控制和显示 分开
-
(个体案例辐射全局)💡💡💡💡💡可以用一个Data SO 中加载了一个列表来作为 整个游戏的物品管理数据库。倘若想添加物品,则直接在SO的列表上添加物品就可以了,不用反复一直创建单个物品的SO
-
可以使用 UI ToolKit 创建一个编辑器(比如物品编辑器)来编辑具体的物品,提高工作效率
-
关于 UI ToolKit 的使用技巧
- 可以勾选视框中的 Reorderable (可重新排序) 以启用排序功能
- 勾选 Show Add Remove Footer 来添加一个脚注来更方便的进行元素的删减添加
- 勾选 Show Border 来显示UI边界
- 可以点 Library 右侧的三个点,来打开编辑器特有功能,便可使用很多编辑器特有功能
- 如果要给编辑器输入一个图片,则 使用 Object Field ,然后在 Type 中输入
UnityEngine.Sprite
便可以输入一个图片精灵了 - 可以勾选 Multiline 来多行显示
- 若要输入bool 值,则可使用 Toggle 控件
- 可以在 Size 选项中使用百分比来控制大小,防止缩放有问题
- 为了使得 ListView 能一直延伸下去,可以将 Flex -> Grow 改成 1 便可
-
超级解耦合:不需要使用 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);
-
-
单例模板
private void Awake() {
if (instance == null) {
instance = this;
} else if (instance != this) {
Destroy(gameObject);
}
}
-
可以使用命名空间来避免 单例乱调用,耦合度提高的情况。(使用单例,则需要using对应的命名空间才可使用)
-
在2D平面中若要实现正确的遮挡关系
- Sprite排序点 设置为 轴心
- 设置好对应的排序图层
-
新背包系统的好处
-
不需要手动传SO了,只需要输入物品的ID即可查找到对应的物品信息,效率高。且图片也不需要自己一个个手动查找添加,好生偷懒。(但是为了编辑方便,结果还是需要手动添加…)
-
玩家的背包系统只需要 存储物品的 ID 和 数量 就可以了,不需要存储别的东西。故这里使用结构体来作为背包物品单位。且在检查背包空位时,只要背包位置上的ID为0,则说明空着物体,更加方便。
-
-
(相当好的优化方法)把背包拾取的 Trigger 检测 安排到player身上,避免了多物品出现的时候多个Trigger判定 来占用不必要的内存。
-
C#初始化的小知识:甚至可以直接用列表初始化,十分方便(这下连构造函数都可以不用写了)
var item = new InventoryItem { itemID = 1001, itemAmount = 1 };
-
💡C# 结构体小知识:C#中无法修改结构体的成员!,如果要达成修改效果,只能用一个新的结构体来覆盖旧的结构体来实现修改效果!
- C# 中只有类可以修改其成员
-
写UI的时候要 单独创建一个场景存放UI (实际工作场合也是需要在单独场景存放UI的)
- 编写UI的技巧
- 在创建新的画布时,更改
Canvas Scaler
中的UI Scale Mode
,修改成Scale With Screen Size
中的 1920 * 1080- 如果是像素游戏,这里还要改下面的 每单位多少像素
- 在需要按钮or图标工整排序时,就可以使用
Layout Group
这个组件来排序- 也可以使用
Layout Element
组件来实现在排序组中标新立异的格子,比如在组中的一个格子单独不受排序的影响等等来忽略布局的影响(用于实现物品栏旁边的背包or各种状态栏) - 在使用
Grid Layout Group
时,可以直接设定每一个格子的大小!!!
- 也可以使用
- 把UI栏中单独的格子(比如各种道具格子,装备格子)设置成预制件,就可以实现牵一发而动全身
- 部分图片UI拉伸后并不美观,可以在
Image
中设置图片类型为sliced
,然后在精灵编辑器中设置拉伸的范围,便可以实现部分(纯色部分)拉伸,而精致部分不会拉伸的效果了 - 如果不想让按钮 or 进度条(做血条的时候)可以有互动效果,则可以勾选取消掉
Interactable
(在做背包空闲格子的时候不需要互动,做血条的时候不需要进度条拖拽功能) - 如果不想让 按钮 or 进度条等 和键盘有互动(用键盘切换格子,调节进度条),则可将
Navigation
调整至none
- 在创建新的画布时,更改
- 编写UI的技巧
-
(有别于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 + 事件名字。通常以私有为主
-
-
如果只是需要拖拽获取控件,则可使用
[SerializeField] private XXXXX
的形式去写,而不是使用public
-
不用
active
来获取物件的活跃状态的bool
值,而是使用activeInHierarchy
来获取- 设置状态也不要使用
XXX.active = false
,而是使用XXX.SetActive(false)
- 设置状态也不要使用
-
启用点按事件(利用Unity自带的事件和接口来实现)(仅对UI有效!!!)
-
使用
using UnityEngine.EventSystems
-
使得控件类 继承 自带接口
IPointClickHandler
public class SlotUI : MonoBehaviour, IPointClickHandler { }
-
可以右键让编译器补全,实现这个接口,也可以自己手动写一个
OnIPointClickHandler(PointerEventData eventData) {}
的函数实现这个接口。最后想实现的东西就直接在这个回调函数中实现就可以了
-
-
启用拖拽事件(与上同)
- 使用
using UnityEngine.EventSystems
- 使得控件类 继承 自带接口
IBeginDragHandler
和IDragHandler
和IEndDragHandler
(甚至要三个接口,分别是:开始拖拽,拖拽过程中,结束拖拽)public class SlotUI : MonoBehaviour, IBeginDragHandler, IDragHandler, IEndDragHandler { }
- 可以右键让编译器补全,实现这个接口(推荐)
- 使用
-
拖拽的实现
-
原理:新创建一个画布,上面添加一个和背包格子大小一致的图片,当拖拽物体时,将该图片移动到格子处且显示对应的图片,松开拖拽时就取消图片的显示。整体是使用了一个障眼法,而不是真的将这个格子移动!(做游戏特有障眼法)
-
初始配置
- 在主Canvas下创建一个新的Canvas
- 关闭Canvas控件中的
Pixel Perfect
- 勾选Canvas控件中的
Override Sorting
(希望排序) - 调高
Sort Order
至 2 ~ 3 - 删除控件
Canvas Scaler
(因为主Canvas已经有了) - 💡💡💡在新的Canvas下新添加的Image物体上,取消其
Raycast Target
(是否会被其他图片遮盖,这里是拖拽所以完全不需要)
-
注意事项
- 在写开始拖拽的时候,建议写一个
SetNativeSize()
来初始化图片原始大小来防止图片失真 - 💡💡💡可以使用
eventData.pointerCurrentRaycast.gameObject
来获取拖拽结束位置,射线所获取到的物体,相当有用了 - 💡💡💡这类单图片的物体的显示就不用改变游戏对象的活跃了,而是
enable or disable
该物体下的图片控件便可控制其显示 - 💡💡💡💡💡当拖拽到对应格子上时,射线获取的可能是格子上的高光物体or显示数字的文本物体,这个时候就需要取消对应的
Raycast Target
来避免射线的错误获取物体(找不到的可能在Extra Setting处)
- 在写开始拖拽的时候,建议写一个
-
相当有用的举一反三的知识
-
拖拽事件的
eventData
可以返回拖拽获取到的游戏对象,可以利用这个实现很多有意思的东西(比如打牌or把一个道具拖拽到一个具体的物体上可以有特殊用途)-
eventData.pointerCurrentRaycast.gameObject
-
-
💡💡💡将鼠标坐标转换为可用的世界坐标
-
// 鼠标对应世界地图坐标 // 新操作系统 var pos = Camera.main.ScreenToWorldPoint(new Vector3(Mouse.current.position.x.ReadValue(), Mouse.current.position.y.ReadValue(), -Camera.main.transform.position.z));
-
-
-
-
鼠标指针划入和划出时的事件(unity自带事件)
- 使用**
IPointerEnterHandler
和IPointerExitHandler
** - 生成这个函数的接口,实现他,便可
- 使用**
-
可以直接用属性的写法直接获取组件,暂且不清楚这种初始化的优先级是否和Awake一致,安全性存疑
private InventoryUI inventoryUI => GetComponentInParent<InventoryUI>();
-
可以用
Content Size Fitter
来使得UI由内容而扩容 -
想要获取TMP文本,则需使用
using TMPro
,其变量名为**TextMeshProUGUI
**(常忘) -
C#崭新的关于Switch的语法糖💡
-
private string GetItemType(ItemType itemType) { return itemType switch { ItemType.Seed => "种子", ItemType.Commodity => "商品", _ => "默认无" } }
利用这个来快速赋值。其中原switch中的default在这里的语法糖中 以下划线为替代!!!!
-
-
因为计算坐标是从锚点开始计算的,所以如果是UITip这类希望UI始终朝上的话,需要将锚点置于底部
-
关于更换装备动画以及更换各种持有物品动画,需使用
Animator Override Controller
-
可以使用控件 Mask 来实现血条 or 其他的遮挡UI效果(时间切换)
- 父物体限定显示范围并添加上控件Mask,子物体显示填充内容
-
C#小知识
- 可以使用如
num.ToString("00")
,里面添"00"
来确保是双位显示
- 可以使用如
-
关于加载场景
-
异步加载场景需使用
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); }
-
SceneManager.LoadSceneAsync
的第二个参数是直接将场景叠加在上一个场景上,而不是直接切换,合适这个项目的场景切换
-
-
默认Panel的图片的角落是有圆角的,如果不想要圆角的话则将图片选择成Square便可
-
实现场景淡入淡出,可以使用控件Canvas Group,然后修改其中的Alpha值便可。这边注意如果是仅仅实现淡入淡出的效果,则应当把Canvas Group中的Blocks Raycasts给取消,这样使得不会遮挡鼠标的点击
-
💡💡💡如果是判断两个float值是否相等,由于float值小数点很多,单单的
==
可能会有bug,所以这里需使用Mathf.Approximately(A, B)
来“近似等于”判断这两个数 -
💡💡💡让一个数缓慢以一个速度变化到另一个数,可以使用
Mathf.MoveTowards(当前值,目标值,速度 * Time.deltaTime)
,可以用来做透明度变化(搭配携程)- 可以用来做渐变效果,配合透明度
-
如果在跨场景用Find来获取控件时,应当将语句写在Start中,如果写在Awake里,会导致Find的时候,对应的控件还没有生成
- 拖拽的方式是无法跨场景的!!!
-
C#语法糖
-
C#的类的初始化甚至可以这样,纯粹的语法糖,真好吃
foreach (var item in items) { SceneItem sceneItem = new SceneItem { itemID = item.itemID, itemNum = item.itemNum }; }
-
-
C#字典的语法糖
-
Dict.ContainsKey(查找的key)
来直接查找key,而不像C++一样明明也是找key,但是名字还是叫Find -
特别好用的
Dict.TryGetValue(想要获取的value所对应的key, out 如果找到了,则将value赋值给这个参数)
,切记第二个参数前要加一个out
。且注意这里的value可以为空int a = 0; // 如果找到值,则直接赋值给a dict.TryGetValue("1", out a);
-
-
虚拟鼠标的做法:又是用一个图片跟随鼠标位置来达成障眼法
-
如何判断鼠标和游戏对象重叠(用于切换鼠标图片)
- 可以直接使用unity自带事件,且不是前面的接口的用法,而是更简单的用法
- 💡💡💡
EventSystem.current.IsPointerOverGameObject()
来判断鼠标是否与对象重叠,简简单单
- 💡💡💡
- 可以直接使用unity自带事件,且不是前面的接口的用法,而是更简单的用法
-
如果需要让代码在编辑模式下进行
-
在全部类上加入一个属性
[ExecuteInEditMode]
-
判断该脚本是否在游戏运行状态运行
if (Application.IsPlaying(this)) {XXX}
-
用宏 去书写 编辑模式特有代码
#if UNITY_EDITOR if (mapData != null) EditorUtility.SetDirty(mapData); #endif
-
当在非游戏状态下用代码修改SO 时,使用
EditorUtility.SetDirty(SO)
可以做到保存当前修改的作用,类似于Ctrl + s
(需注意要using UnityEditor
)
-
-
对于单个瓦片做鼠标射线判断的时候,就需要将鼠标从 屏幕坐标 转换为 世界坐标 再转换为 网格坐标,才能对网格做出判断
-
做出屏幕坐标转换世界坐标时,需要获取到相机Camera,而这里并不需要GetComponent,而是
private Camera mainCamera; private void Start() { mainCamera = Camera.main; }
便可获取主摄像机。注意这里需要将主摄像机的标签设置为
MainCamera
- 转换世界坐标需要一个camera作为参照,转换网格坐标需要一个grid作为参照
// 屏幕坐标转换为世界坐标
mouseWorldPos = mainCamera.ScreenToWorldPoint(Mouse.current.position.ReadValue());
// 世界坐标转换为网格坐标
mouseGridPos = currentGrid.WorldToCell(mouseWorldPos);
-
当需要根据很多详细信息去寻找一个物体(物品细节or属性)的时候,如要根据 坐标,所在场景,甚至名字,然后要根据这些具体信息返回。则可以使用 字符串的形式 ("X1Y2Scene1"这种字符串就包含了上面的所有信息) 串联起全部信息,然后利用一个字典来根据这些信息返回具体的物品细节or属性
- 这可以存储查找各种瓦片地块,或者卡片游戏里的卡片
-
💡💡💡💡💡考虑使用距离的平方,而不是距离
-
float distanceSqrd = (transform.position – other.transform.position).sqrMagnitude; if (distanceSqrd < (targetDistance * targetDistance)) { // do stuff }
-
-
🍅🍅🍅使用
Vector3.MoveTowards
的正确方法-
✔
transform.position = Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
注意这里速度还要* Time.deltaTime
-
❌
Vector3.MoveTowards(transform.position, targetPos, speed * Time.deltaTime);
不是直接摆一个函数就可以移动坐标了的!!!!
-
-
💡💡💡💡💡为了不让命名冲突,以后写东西都写在自己的命名空间里!!!(之前的项目就有过"Mouse"的命名冲突!)
-
种地更改瓦片(终于到了种地环节)
- 使用
Tilemap.SetTile(Vector3Int pos, TileMaps.TileBase tile)
来实现更换瓦片- 这里的更换瓦片甚至可以是
RuleTile
,帮大忙了 - 记得
using UnityEngine.Tilemaps
- 这里的更换瓦片甚至可以是
- 设置规则瓦片的时候不要只设置九宫格的规则,还应当设置单行和单列的规则瓦片的规则!!!
- 利用 搜索标签的方式 去获得当前的瓦片地图(因为场景会切换,所以当前最好的方式就是直接
FindWithTag
)(可以放在切换场景的事件中去调用,去寻得当前瓦片地图) - 关于52人物动作的课程 先跳过,若有需求,则再细看(因为主要想用状态机实现,还是看需求吧)
- 会使用
Animator Override Controller
- 会使用
- 保存地图数据(场景反复切换后仍保存)
- 记得总的地图的信息都是保存在字典里的,所以要修改字典
- 每次加载场景要根据字典
- 若想要根据时间而变化地图的细节,则直接写一个比如 一天过去后 的事件来调用就可以了
- 使用
-
💡💡💡C#语法糖:C#的字符串可用直接用
str.Contains("123")
来查找"123"
有没有出现在str
中,十分强大 -
C# 知识 :当C#里没有迭代器这种好东西,如何遍历字典这类数据结构呢?
-
其中一个最懒的方法就是使用
foreach
foreach (var entry in sample_Dict) System.Console.WriteLine(entry.Key + ":" + entry.Value);
foreach
得到的每一个元素其实是一个 键值对,需使用键值对.key 键值对.Value
的方式来访问
-
-
C# 知识 or 语法糖 :对于一个固定规则获取的变量,对其初始化可以这么写
-
// 记录一个总数 public int totalGrowthDays { get { int amount = 0; for (int i = 0; i < growthDays.Count; ++i) { amount += growthDays[i]; } return amount; } }
语法糖实在是太甜了,简单方便又快捷,代码优美的不行
-
-
💡💡💡如果
[Header("XXX")]
放太多了,想要来一点层次感,则可以用**[Space]
**直接创造出一个空行,来营造层次感(绝了,又学到一个好东西) -
💡C#又一语法糖,通过lambda表达式来限制 列表查找 的条件
public ItemDetails FindItemByID(int ID) { return itemDataList.itemDetailsList.Find(i => i.itemID == ID); }
-
💡💡💡通过通过物理方法,获取鼠标当前位置的碰撞体数组(项目里用于收获植株)(不一定是鼠标,这里是获取一个点上的所有碰撞体数据,然后进而去更改其他脚本)
Collider2D[] cds = Physics2D.OverlapPointAll(mouseWorldPos);
-
新输入系统的用法Get
-
可用
Key
来设置一个专门的键位public Key key
-
// 便可用这种方式实现自定义快捷键!!!! Keyboard.current[key].wasPressedThisFrame /* Keyboard.current[key.A].wasPressedThisFrame 等同于 Keyboard.current.KeyA.wasPressedThisFrame
-
-
粒子系统tip
-
在 发射器速度模式 上,如果不是模拟刚体的话,就使用
transform
模式(对坐标的计算)(翻译做变换)就好了,节省资源 -
可以调整 生命周期内的颜色 来达成粒子效果在各个阶段具有不同颜色和透明度的特点,可以做的非常漂亮
-
可以调整 生命周期内的旋转 来达成旋转
-
可以调整 纹理表格动画 来替换粒子效果的图片
-
可以调整 噪音(noise,噪点,什么奇怪翻译) 来调整粒子的晃动效果,可以以此实现落叶的晃动飘落效果,美感++
-
如何管理粒子系统:使用 枚举enum + 对象池
-
enum一个粒子类型,用于管理
-
可以在 渲染器 中调整粒子特效的图层,使得不会被遮盖
-
-
对象池的使用
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); // 💡💡💡 }
-
可直接使用
?.
直接保证函数实施者的非空,例如currentCrop?.BeTheOne()
-
AStar
算法-
A*的节点比较需要用到
using System
中的接口IComparable
-
数据结构
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; } }
-
-
更新点 C#小知识
-
HashSet
(其实就是C++里的Set),目的是使用查找函数Contain()
的时候,HashSet
的速度远快于List
-
SortSet
(这个和C++原生Set一样会排序,C#的原生Set不会排序!!!) -
当在C#中,一个函数想返回两个值的时候,就可以利用小特性,在参数中添加
out
的前缀,表明是要返回的值(不用像C++一样要整一个结构体了)(可直接写在函数形参上) -
遍历栈也可以直接
foreach
来直接遍历,例如foreach (var step in npcMovementStepStack) {}
-
// 顶级语法糖,这个TryPop直接把判断非空给省了,完爆Pop if (dailogueStack.TryPop(out DialogPiece result)) {}
-
C#中的字符串转换成整型的方法
-
int amount = Convert.ToInt32(tradeAmount.text);
-
-
-
当在写时间戳的时候,可以利用 C# 自带的 时间封装单位(甚至C#自带),主要用处是 用来装各种时间单位,作为一个总体的时间来使用
-
using System
-
public TimeSpan GameTime => new TimeSpan(gameHour, gameMinute, gameSecond); // 只是其中一种用法
-
-
协程小知识[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uNJWyduE-1685451275081)(D:_我的MarkDown笔记\图片存放\image-20230322194048259.png)]
-
基于麦田物语的制作场景时的小tips
- 创建 MapData 添加到 Properties 当中并设置好 origin 和 size 还有场景名字
- MapData 数据需要添加到 GridMapManager 的数据列表当中
- 绘制地图也要绘制 Collision|NPCObstacle
- 添加 Bounds 摄像机边界 别忘记 Tag
- 创建 ItemParent 和 CropParent 别忘记 Tag
- Teleport 要设置好目标场景和目标坐标
- 创建好了场景别忘记加到 Build Settings 当中
- 设置好 SceneRoute 路径点
-
可使用
[TextArea]
直接给字符串创建一大片输入区域,方便输入 -
UnityEvent
的使用using UnityEngine.Events
- 直接创建一个
public UnityEvent OnFinishEvent
。就会得到一个类似于按钮的回调函数事件的一个窗口 - 这类自制回调函数可用来调用各种窗口,开启各种音乐,CG,非常好用了
-
(
DoTween
)如果是Text类型,可以直接用DoText()
来实现打字效果,但却不支持 TMP,但可以用DoTween
通用方法实现-
通用方法:
DOTween.To( getter, setter, endValue, float duration )
,前两个参数是函数指针,用lambda会好用点 -
参数解释
-
你要对哪一个目标进行调整
-
你要对这个目标做什么操作,以让他达到变化的目的(一般直接等式就好)
-
最终的要变化成的目标值
-
这个过程所用的时间
-
-
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
都可以,就很强大
-
-
一个用代码手动添加 按钮点击的回调函数 的方法,这种方法主要好处是可以添加
private
的函数,增强保密性// 手动添加 按钮点击的回调函数 cancelBtn.onClick.AddListener(CancelTrade); private void CancelTrade() { gameObject.SetActive(false); }
-
如果一个实体在测试窗口能看见,但在Game窗口下看不见,则可能是 摄影机 的 z轴 和 实体的 z轴间的问题(摄影机拍不到实体,故导致)
- 原因:在生成实体时,用的是鼠标的坐标去生成,而摄影机默认z坐标是 -10 ,而鼠标的坐标的默认z坐标也是 -10,就导致摄影机拍不到物体(平行了)
- 解决方法:在生成实体的时候,修改实体的z坐标大于 -10 便可
-
如果想让 初始化 在切换场景时启用,可以写在
OnEnable
中,因为OnEnable
在拆卸装载场景时都会调用,而Start
只会调用一次 -
关于升级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)]改成这个就可以了
- 包管理器里搜
-
关于URP的使用
using UnityEngine.Rendering.Universal
- 一般使用 全局光源 来做昼夜变化的总光源
- 用一个数据类型来记录单个节点的灯光,然后用一个列表来收集这些数据。这种方法十分常用[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uDLL8whD-1685451275082)(D:_我的MarkDown笔记\图片存放\image-20230403170319811.png)]
-
可用
[Range(0f, 1f)]
前缀来使用滑动条来控制数值 -
Audio Mixer 中的各个元素的音量大小是需要 手动右键 暴露出来的,这样才能进行代码上的修改
-
Timeline
using UnityEngine.Playables
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RB3vLDi8-1685451275083)(D:_我的MarkDown笔记\图片存放\image-20230411191530611.png)]用于自定义timeline的行为
-
可使用这种方式[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhMVTAI5-1685451275084)(D:_我的MarkDown笔记\图片存放\image-20230411212040772.png)]使得某个对象处于最下层,得到优先渲染,实现类似 叠层 的效果
-
健忘点:
Application.Quit();
用于退出游戏,别老是忘记 -
有关存档
- 为了存储 列表 和 字典 的数据,故使用新的工具
- 在包管理器中 直接
add for URL
,输com.unity.nuget.newtonsoft-json
便可
- 在包管理器中 直接
- 为了存储 列表 和 字典 的数据,故使用新的工具
-
对于多场景叠加的设计模式
- 在打包的时候,是只加载一个场景的,所以像UI之类的场景需要手动添加进去