初始化obs/Unity仓库
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
#### 项目设置
|
||||
- 版本使用2023.3及以上版本
|
||||
- 在preference中的scene view中勾选 creat objects at origin选项
|
||||
- 在package Manager中调整相关内容
|
||||
#### 素材导入
|
||||
- Icon图标
|
||||
- DOTween
|
||||
- Colourful Hierarchy Category GameObject
|
||||
- 课程自带素材
|
||||
@@ -0,0 +1,66 @@
|
||||
本节的难度比较大,基本上全部都是代码相关的问题,应该着重弄懂,可以默写.
|
||||
在上节课当中我们已经实现了对象池,可以从对象池中拿到卡牌,释放卡牌.这节课我们需要实现的内容是制作卡牌库实现抽卡.
|
||||
这里我们首先要弄明白,卡牌库,抽牌堆,手牌,弃牌堆这些内容.
|
||||
我们在游戏不断进行过程中,每一个关卡胜利之后,我们都会获得一张卡牌,去填充我们的卡牌库.
|
||||
在每一个关卡开始的时候,我们的抽牌堆会进行更新,将我们的卡牌库的所有牌都充入其中,同时抽出几张牌放入我们的手牌当中,当手牌打出就进入弃牌堆,当游戏结束剩余的卡牌也要进入弃牌堆,因此上述逻辑是我们本节课的关节内容.
|
||||
我们依照上述内容进行本节课的展开
|
||||
### 制作卡牌库
|
||||
我们的游戏是在不断的打怪升级中发展的,因此我们的卡牌库也是不断更新的,但是我们游戏最开始的时候有一套最初始的卡牌库,因此游戏当中的卡牌库一共有两个.
|
||||
1.游戏最开始的卡牌库
|
||||
2.随着游戏不断进行不断更新的卡牌库(当前卡牌库)
|
||||
确定好了卡牌库,我们要确定卡牌库的内容,我们的卡牌库要储存各种各样的卡牌,还要储存他们的数量,简化就是卡牌的种类和卡牌的数量,因此我们使用SO文件创建卡牌库,他当中有一个列表,列表的类型是自定义的类型,这个类型只有两个字段,一个CardDataSO表明类型,一个int表明数量.
|
||||
同时在我们的CardManager当中,声明这两个卡牌库,并且在初始化的时候,将游戏最开始的卡牌库赋值给当前卡牌库.
|
||||
|
||||
### 实现抽卡
|
||||
抽卡我们需要单独创建一个空物体,并且为他创造一个脚本来实现抽卡的相关逻辑.
|
||||
我们的抽卡,就是从抽牌堆中拿出一张牌,放入我们的手牌堆当中.
|
||||
因此我们需要这三个列表.
|
||||
```
|
||||
private List<CardDataSO> drawDeck = new(); //抽牌堆
|
||||
|
||||
private List<CardDataSO> disCardDeck = new();//弃牌堆
|
||||
|
||||
private List<Card> handCardObjectList = new();//当前的手牌(每回合)
|
||||
```
|
||||
|
||||
同时在游戏开始的时候我们要初始化我们的抽牌堆,这样就需要我们有CardManager,来初始化我们的抽牌堆,我们将CardManager也创建.
|
||||
在这节课当中,我们这个脚本需要实现的逻辑就是
|
||||
一.初始化抽牌库
|
||||
二.抽卡
|
||||
|
||||
1.初始化抽牌库我们需要创建一个函数,在start中调用,这个函数的内容就是使用循环语句,将
|
||||
CardManager当中的currentLibrary一一赋值给我们的抽牌堆,代码如下
|
||||
|
||||
```
|
||||
public void InitializeDeck()
|
||||
{
|
||||
drawDeck.Clear();//对抽牌堆进行清理
|
||||
//循环遍历当前的卡牌库,放进我们的抽牌堆
|
||||
foreach (var enter in cardManager.currentLibrary.cardLibraryList)
|
||||
{
|
||||
for (int i = 0; i < enter.amount; i++)
|
||||
{
|
||||
drawDeck.Add(enter.cardDataSO);
|
||||
}
|
||||
}
|
||||
//TODO: 洗牌/更新抽牌堆和弃牌堆
|
||||
}
|
||||
```
|
||||
2.抽卡就是需要我们从抽牌堆当中得到一个SO的文件,然后从对象池中生成一个卡片,并且将这个SO文件用来初始化这个卡牌,代码如下
|
||||
```
|
||||
public void DrawCard(int amount)
|
||||
{
|
||||
for (int i = 0; i < amount; i++)
|
||||
{
|
||||
if (drawDeck.Count == 0)
|
||||
{
|
||||
//TODO: 洗牌/更新抽牌堆和弃牌堆
|
||||
}
|
||||
CardDataSO currentCardData = drawDeck[0];
|
||||
drawDeck.RemoveAt(0);
|
||||
var card = cardManager.GetCard().GetComponent<Card>();//为什么这里会实例化出来一个新的card对象呢?
|
||||
card.Init(currentCardData);
|
||||
handCardObjectList.Add(card);
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1 @@
|
||||
这一节课同样的,没有新知识,全部都是代码思路.
|
||||
@@ -0,0 +1,13 @@
|
||||
### 1.在屏幕坐标转换为世界坐标的过程中,Z轴不同应该如何处理
|
||||
在2D游戏中,我们的摄像机Z坐标和场景的坐标一般是不同的,比如摄像机Z坐标为-10,场景坐标为0,那么如过我们在坐标转换的过程中,不特意的设置Z轴将产生一些问题.
|
||||
比如
|
||||
```
|
||||
Vector3 screenPos = new(Input.mousePosition.x, Input.mousePosition.y, 0);
|
||||
|
||||
Vector3 worldPos = Camera.main.ScreenToWorldPoint(screenPos);
|
||||
```
|
||||
上述代码可以在单击左键的时候进行坐标转换,但是我们传入的z轴为0,此时我们转换为世界坐标,他的z轴为-10.
|
||||
因为我们的摄像机坐标为-10,传入0相当于使用摄像机的坐标.如过我们想让我们的坐标在场景当中,也就是0应该怎么办呢?
|
||||
我们将z传入为10,也就是将屏幕坐标往z轴正方向移动10个单位,这时候获得的世界坐标z就是0.
|
||||
### 2.Unity当中的Physics 2D raycaster组件作用和原理
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
### 疑问点
|
||||
- 为什么RoomPrefab需要创建一个子物体sprite,在sprite上创建sprite Renderer,而不是直接在roomprefab上创建sprite Renderer?
|
||||
- 为什么调整图片大小,不调整父物体的scale,而是调整sprite的scale
|
||||
- 为什么碰撞体要添加在父物体身上?
|
||||
- 获取组件在哪个生命周期函数中获得?这些生命周期函数有什么不同?
|
||||
- 为什么在assets中创建了一个脚本Enums,里面专门写各种各样的枚举类型,别的脚本可以直接使用这些枚举类型
|
||||
- 我们的枚举类型声明时候没有特定指出生命在了哪个命名空间当中
|
||||
- 这是我们第一次使用SO文件作为参数配置,他的头文件每个参数代表的含义是什么?
|
||||
### 过程
|
||||
本质上是创建房间的prefab,以便于随机地图生成时直接实例化预制体,所以我们要创建一个房间的prefab,它上面需要挂载一个Room的脚本,里面包含了他自己的坐标位置X,Y,他的房间类型,他的房间状态。
|
||||
我们的房间状态,房间类型都是枚举类型,所以我们要写两个枚举类型。
|
||||
房间的类型,他需要有他自己的icon,有自己的类型,以及自己需要加载的场景,这是每一个房间都会有的,所以我们写为SO文件,以便于性能上的优化。
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
### 疑问
|
||||
- 为什么我的列表(列表类型为自定义的类)不能显示在inspector当中
|
||||
- 如何实现的枚举类型在inspector的多选,他的内部机制是什么?
|
||||
- 屏幕数值大小由摄像机决定,摄像机size为6,则高度为12,长度由宽高比可计算得出。
|
||||
### 过程
|
||||
设置地图配置表的过程相对简单,我们仍然需要使用scriptableObject这项技术。创建配置表的过程中,我们建立一个类,这个类的内容是我们每一列房间的数量最大值最小值,以便于我们可以随机生成,同时还需要有我们的房间类型,然后我们使用列表,使用新建立的类为类型,列表的每一个成员都代表了一列房间,一共7列房间,每一列都需要有他可以承载的最大值和最小值,以及他可以包含的房间类型,并且是可以多选的房间类型。
|
||||
完成上述工作之后,我们建立一个配置文件,将每一列的配置信息都做好就完成了本项配置表。
|
||||
@@ -0,0 +1,37 @@
|
||||
这节课在点击回合转换按钮,清空卡牌的时候出现的错误,当时的代码是
|
||||
```
|
||||
public void OnPlayerTurnEnd()
|
||||
{
|
||||
for (int i = 0; i < handCardObjectList.Count; i++)
|
||||
{
|
||||
DiscardDeck(handCardObjectList[i]);
|
||||
}
|
||||
handCardObjectList.Clear();
|
||||
|
||||
}
|
||||
```
|
||||
由于我们使用DiscardDeck函数,每次都会将手牌列表(handCardObjectList)的第一位移除,这样会导致我们的部分卡牌不能正确的被移除.
|
||||
以下是Deepseek的解释.
|
||||
在`OnPlayerTurnEnd`方法中,当遍历手牌列表并调用`DiscardDeck`时,正向循环会导致部分卡牌未被处理。每次移除元素后,列表的索引和长度发生变化,导致后续元素被跳过。以下是解决方案:
|
||||
|
||||
**步骤解释:**
|
||||
|
||||
1. **问题分析:** 正向遍历列表时,移除元素会导致后续索引错位,部分卡牌未被处理。
|
||||
|
||||
2. **解决方案:** 改为从后向前遍历列表,这样移除元素不会影响未处理的索引。
|
||||
|
||||
3. **代码调整:** 修改循环结构,确保所有卡牌都被正确弃用。
|
||||
|
||||
修改后的代码为:
|
||||
```
|
||||
public void OnPlayerTurnEnd()
|
||||
{
|
||||
// 从后向前遍历,避免索引错位
|
||||
for (int i = handCardObjectList.Count - 1; i >= 0; i--)
|
||||
{
|
||||
DiscardDeck(handCardObjectList[i]);
|
||||
}
|
||||
// 清空列表(此时应已为空,但确保万无一失)
|
||||
handCardObjectList.Clear();
|
||||
}
|
||||
```
|
||||
@@ -0,0 +1,5 @@
|
||||
首先在玩家类当中创建一个蓝条,并且创建一个int variable类型的变量,名字为Player Mana.
|
||||
这个类型在每次蓝条有消耗的时候都会调用事件.
|
||||
因此我们创建一个新的时间,在游戏面板中监听,当数值变化的时候,修改我们游戏面板当中的数值.
|
||||
|
||||
我们打出一张卡牌会有消耗,我们创建一个int variable类型的事件,在打出卡牌的时候,raise这个事件,然后我们在Player当中监听,减去这个消耗,更新蓝条.
|
||||
@@ -0,0 +1,23 @@
|
||||
这节课我们要做的内容是,在玩家回合的时候,敌人会显示他的意图,在敌人回合的时候,敌人会执行.
|
||||
|
||||
我们现在说如何在玩家回合,敌人显示他的意图.
|
||||
|
||||
首先敌人的意图应该是有多个的,我们要在众多意图中随机抽取一个,所以我们就需要建立一个列表,提前在列表中写明他的意图,我们随便抽取一个.
|
||||
|
||||
敌人的意图也就是一个效果Effect,但是我们还要将他显示出来,因此这个列表还需要一个图标,所以我们建立一个结构体struct,他包含一个图标和一个Effect.
|
||||
|
||||
然后我们建立一个SO文件,用来储存这个列表.
|
||||
|
||||
建立之后我们去到Unity当中对这个SO进行设置,并且在Enemy中设置声明获取这个SO文件.
|
||||
|
||||
在Enemy当中我们需要做一件事,就是在玩家回合开始的时候,我们需要在SO中随机抽取一个意图,并且设置一个变量CurrentAciton储存他.
|
||||
|
||||
然后来到血条的代码当中,我们设置一下敌人意图的图标和数字.并且在代码中获取他,将他初始化的时候关闭显示.
|
||||
|
||||
|
||||
我们创建一个新的代码,获取敌人身上的当前意图,将这个意图的图标和数字设置在血条上,当玩家回合开始的时候调用这个函数.
|
||||
|
||||
做完这一切我们在玩家回合的时候就可以显示敌人的意图了,但是我们还没有执行他,我们现在来设置如何在敌人回合执行这些意图.
|
||||
|
||||
我们在Enemy当中创建一个新的方法,他在敌人回合开始的时候调用,他的内容是检测这个意图是对自己还是对敌人,然后执行相应的逻辑.
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
首先在character中我们需要声明好一个事件,当玩家死亡的时候调用这个事件.
|
||||
这个事件监听的函数在GameManager当中,我们检测是玩家死了还是敌人死了,玩家死了就执行玩家死了的逻辑,敌人死了就执行敌人死了的逻辑.
|
||||
|
||||
然后我们创建两个事件他们分别是游戏胜利,游戏失败的事件.我们需要这两个事件来启动相应的逻辑.
|
||||
@@ -0,0 +1,7 @@
|
||||
生成房间基本上没有疑问点,重点难点都是数学问题,要看个人如何思考。
|
||||
首先我们要创建一个空物体,创建一个生成房间的脚本挂载在上面,现在我们重点考虑脚本如何写,房间的生成非常简单使用Instantiate实例化预制体即可,关键点在于房间的位置如何计算。
|
||||
我们只需要确定第一列第一个房间的位置,第一列的剩余房间可以加上间隔去确定,其余列也可以加上水平间隔去确定。
|
||||
我们先确定第一个房间的位置,在确定水平间隔以及垂直间隔。
|
||||
第一个房间的x坐标我们可以这样计算,-screenwidth/2,得到最左边的坐标位置.我们加上一个border,得到第一个房间的x坐标,然后用同样的方式计算y坐标.
|
||||
screenheight/2得到最上边的位置,我们在减去screenheight/(amount+1),得到了我们的第一列第一个房间的位置.
|
||||
我们在每次大循环中加上X坐标间隔,在每次小循环中加上y坐标间隔就可以实现房间的生成.
|
||||
@@ -0,0 +1,5 @@
|
||||
生成地图上的连线,并没有什么疑惑点,操作也比较简单.
|
||||
我们创建一个空对象,添加上line renderer组件,设置其大小,将其设置为预制体,在后面通过代码设置他的起始点和最终点.
|
||||
我们的连线规则是,两列之间随意连线,确保每一个房间被连接以及连接出去,实现这样的方式是通过一个foreach循环,将第一列的每一个房间都随机的连接到第二列的某一个房间,当然这样会有问题,第二列的某个房间可能没有被连接到,所以我们需要通过使用HashSet列表,这个列表不会储存重复的成员,每次第二列连接到一个房间,我们就将该房间加入这个列表,列表外的房间就是没有连接的房间,我们将没有连接的第二列的房间,反向连接到第一列即可.
|
||||
然后就是需要对连线的材质进行处理,我们想让连线动起来,我们创立一个unlit的材质,他表示无灯光的材质,也就是可以显示透明的材质,将我们的line材质拖进去,通过脚本调整材质球的offset实现动画效果.
|
||||
我们创建一个line脚本挂载到lineprefab上,通过offset不断增加实现动画效果.
|
||||
@@ -0,0 +1,22 @@
|
||||
### 疑问
|
||||
- addressable场景有什么作用?
|
||||
- 什么是字典?
|
||||
- 场景激活有什么作用?
|
||||
- Enum.Parse是什么?有什么作用?
|
||||
### 过程
|
||||
这节课其实是2节内容,第一节内容是设置每个地图,第二个内容是设置房间的随机类型.
|
||||
我们房间的随机类型的实现过程如下:
|
||||
|
||||
首先我们每一列都储存在mapBluePrints列表当中,每个列表成员有当前列表可包含的所有房类型,我们要做的就是从当前列可以允许的类型当中随机出来一个类型赋值給我们的房间.
|
||||
|
||||
这样做首先暴露出来的问题是,我们随机一个类型,类型是一个代号,他并不包含数据,我们包含数据的成员是我们的RoomDataSO,所以我们要让这两者产生联系,通过字典的键值对.每一个RoomType都要和他的值RoomData相联系.
|
||||
|
||||
我们建立一个RoomDataSo类型的列表,将所有的房间RoomData都放进去,然后通过foreach语句一一连接.
|
||||
|
||||
做好了上一步之后,我们只需要在当前列的类型范围当中,随机一个类型,通过字典获得roomdata,通过room的setup函数设置类型和图标.
|
||||
|
||||
我们如何在当前列中随机一个房间类型?
|
||||
|
||||
我们的房间类型是可以多选的,所以我们可以将他转换为字符串,然后通过字符串切割成多个字符串,存入一个数组当中,然后在数组中随机选择一个索引值,得到一个随机的字符串,通过Enum.Parse函数,将字符串转换为enum类型的值.
|
||||
|
||||
通过这个随机的roomtpye,连接字典得到这个类型的roomdata,将这个roomdata传入setup函数就可以实现随机地图的产生.
|
||||
@@ -0,0 +1,148 @@
|
||||
## 疑问
|
||||
### 1.[TextArea]有什么作用?
|
||||
`[TextArea]` 是一个用于 **增强字符串字段编辑体验** 的重要特性,他的作用是:
|
||||
- 将普通的单行输入框变为 **可伸缩的多行文本区域**
|
||||
- 支持 **回车换行** 和 **文本滚动条**
|
||||
- 默认显示 3 行(可自定义最小/最大行数)
|
||||
2.BaseEventSO当中为什么不使用UnityEvent而是用UnityAction.
|
||||
![[Pasted image 20250222185154.png]]
|
||||
# **过程**
|
||||
|
||||
## Unity 事件系统框架解析(广播-监听模式)
|
||||
|
||||
## 系统架构图
|
||||
|
||||
```markdown
|
||||
[按钮对象] --广播--> [ObjectEventSO] --通知--> [门对象]
|
||||
| |
|
||||
|-- 触发按压动作 |-- 作为事件通道
|
||||
|-- 连接监听器
|
||||
```
|
||||
|
||||
## 一、核心组件说明
|
||||
|
||||
### 1. 广播系统 (EventSO)
|
||||
|
||||
#### 1.1 BaseEventSO<T> 基类
|
||||
|
||||
```csharp
|
||||
// 核心功能:管理事件订阅和广播机制
|
||||
public class BaseEventSO<T> : ScriptableObject
|
||||
{
|
||||
[TextArea] // 可视化描述字段
|
||||
public string description;
|
||||
|
||||
// 事件订阅集合(使用object类型保持灵活性)
|
||||
public UnityAction<T> OnEventRaised;
|
||||
|
||||
// 调试信息字段
|
||||
public string lastSender;
|
||||
|
||||
// 事件触发方法
|
||||
public void RaiseEvent(T value, object sender)
|
||||
{
|
||||
OnEventRaised?.Invoke(value);
|
||||
lastSender = sender.ToString(); // 记录最后发送者
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**关键设计:**
|
||||
|
||||
- 使用 ScriptableObject 实现全局事件通道
|
||||
- 泛型设计支持多种参数类型
|
||||
- lastSender 字段用于调试追踪
|
||||
|
||||
#### 1.2 ObjectEventSO 具体实现
|
||||
|
||||
```csharp
|
||||
// 创建菜单项便于生成资产
|
||||
[CreateAssetMenu(fileName = "ObjectEventSO", menuName = "Event/ObjectEventSO")]
|
||||
public class ObjectEventSO : BaseEventSO<object> { }
|
||||
```
|
||||
|
||||
**使用场景:**
|
||||
|
||||
- 需要传递任意类型参数时
|
||||
- 通用对象交互场景
|
||||
- 快速原型开发阶段
|
||||
|
||||
### 2. 监听系统 (EventListener)
|
||||
|
||||
#### 2.1 BaseEventListener<T> 基类
|
||||
|
||||
```csharp
|
||||
public class BaseEventListener<T> : MonoBehaviour
|
||||
{
|
||||
[Header("事件配置")]
|
||||
public BaseEventSO<T> eventSO; // 绑定的事件资产
|
||||
|
||||
[Header("响应事件")]
|
||||
public UnityEvent<T> response; // 可视化配置的响应事件
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
if(eventSO != null)
|
||||
eventSO.OnEventRaised += OnEventRaised;
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
if(eventSO != null)
|
||||
eventSO.OnEventRaised -= OnEventRaised;
|
||||
}
|
||||
|
||||
private void OnEventRaised(T value)
|
||||
{
|
||||
response.Invoke(value); // 转发事件到UnityEvent
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**生命周期管理:**
|
||||
|
||||
- OnEnable:注册监听
|
||||
- OnDisable:注销监听
|
||||
- 自动化的订阅管理防止内存泄漏
|
||||
|
||||
#### 2.2 ObjectEventListener 具体实现
|
||||
|
||||
```csharp
|
||||
public class ObjectEventListener : BaseEventListener<object> { }
|
||||
```
|
||||
|
||||
**扩展说明:**
|
||||
|
||||
- 可创建特定类型的监听器(如IntEventListener)
|
||||
- 保持基类的泛型灵活性
|
||||
|
||||
## 二、工作流程详解
|
||||
|
||||
### 1. 配置阶段
|
||||
|
||||
1. 创建 EventSO 资产
|
||||
- Assets右键 → Create → Event → ObjectEventSO
|
||||
2. 配置广播者(按钮)
|
||||
|
||||
- 添加事件触发脚本
|
||||
|
||||
```csharp
|
||||
public class ButtonPressHandler : MonoBehaviour
|
||||
{
|
||||
public ObjectEventSO pressEvent;
|
||||
|
||||
void OnMouseDown()
|
||||
{
|
||||
pressEvent.RaiseEvent(this, "DoorButton");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. 配置监听者(门)
|
||||
- 添加 ObjectEventListener 组件
|
||||
- 绑定同一个 EventSO 资产
|
||||
- 配置 Response 事件链(示例):
|
||||
- 添加 GameObject.SetActive(false)
|
||||
- 添加 Animator.Play("OpenDoor")
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
## 疑问
|
||||
### 1.addressable场景有什么作用?如过不addressable会怎么样?
|
||||
|
||||
|
||||
使用addressable需要引入包
|
||||
|
||||
加载房间也是一个重点和难点,因为他涉及到了我没有学习过的场景加载方面的内容
|
||||
传统的场景异步加载使用的是协程而不是async/await,因为协程是Unity集成的,他的性能比C#的Task差,但是在2023更新之后,将c#的Task更新为了Awaitable,他的性能比C#的Task好得多,因此教程使用了Awaitable来进行场景加载,这节笔记首先讲解课程所使用的方法,在讲解协程的老方法.
|
||||
|
||||
同时我在刚开始对Task非常疑惑,Task类型其实就是一个异步操作,我们在使用async/await的时候需要将其中的方法设置为Task,在Unity更新之后,我们可以用Awaitable代替Task.
|
||||
|
||||
协程是使用yield来实现异步的等待,Awaitable是使用await来实现异步的等待.
|
||||
|
||||
# **使用Awaitable实现场景切换**
|
||||
|
||||
本节课加载房间的流程:首先创建我们所有需要的房间并且将他们设置为Addressable,然后在
|
||||
Windows->Asset Management->Addressables->Groups当中简化我们房间的名字.
|
||||
我们需要在场景中创建一个空对象命名为SceneLoadManager用来控制我们的场景加载,在类中我们首先声明两个变量,他们的类型是AssetReference,一个名为currentScene一个为map,标明一个是需要加载的场景,另一个是地图场景.
|
||||
我们目前处于地图场景当中,当我们点击了房间的图标的加载进入图标的房间,当我们点击房间的时候通过事件会将当前房间的数据传入到我们这个类当中,所以我们可以知道我们需要加载的场景是什么,同时将我们的currentScene赋值为当前需要加载的场景,然后我们卸载Map,加载currentScene,同时将他设置为setactiveScene.
|
||||
|
||||
加载房间和卸载房间的方法,首先他的类型是async Awaitable,这是2023以后推出.
|
||||
加载房间中我需要异步加载当前场景
|
||||
```
|
||||
private async Awaitable LoadSceneTask()
|
||||
|
||||
{
|
||||
|
||||
var s = currentScene.LoadSceneAsync(LoadSceneMode.Additive);
|
||||
|
||||
await s.Task;
|
||||
|
||||
if (s.Status == AsyncOperationStatus.Succeeded)
|
||||
|
||||
{
|
||||
|
||||
SceneManager.SetActiveScene(s.Result.Scene);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
```
|
||||
我们通过AssetReference.LoadSceneAsync,加载场景, await s.Task等待加载完成,然后通过一个if语句判断,如过加载成功,就将当前场景切换为currentScene
|
||||
```
|
||||
private async Awaitable UnLoadSceneTask()
|
||||
|
||||
{
|
||||
|
||||
await SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene());
|
||||
|
||||
}
|
||||
```
|
||||
卸载场景也是一样的.
|
||||
在本教程当中,由于设置了一个持久化场景,我们其余的Map,Room场景都是第二第三个场景,并且每一个场景都包含很多的东西,因此我们在加载场景的过程中需要先卸载,在加载场景.
|
||||
|
||||
## **使用协程进行场景切换**
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,102 @@
|
||||
保存地图场景,顾名思义,他的本质就是保存,将我们的房间数据保存到另一个物体当中,并且我们可以从这个物体中读取数据.
|
||||
|
||||
我们已经使用过了scriptableobject,知道他是一种数据容器,可以储存和保存数据,因此考虑使用SO文件来储存地图数据.
|
||||
|
||||
我们需要保存的地图数据只有2个,第一个是房间,第二是连线.地图当中有非常非常多的房间和连线,所以我们肯定会使用列表来储存,但是没有一种类型可以支持我们储存这些数据,因此我们需要创建一个自定义的类来储存房间和连线.
|
||||
|
||||
我们先考虑一下储存房间需要哪些信息,他的行和列,他的X.Y坐标,以及他的roomData数据和RoomState.那我们的连线呢?只需要保存他的起始坐标和终止坐标就可以了.虽然我们的游戏是2D的,但是连线的位置坐标却是3D的,vector3类型的无法被序列化,我们需要创建一个可以帮助v3序列化的工具类.
|
||||
|
||||
```
|
||||
using UnityEngine;
|
||||
|
||||
[System.Serializable]
|
||||
|
||||
public class serializeVector3
|
||||
|
||||
{
|
||||
|
||||
public float x, y, z;
|
||||
|
||||
public serializeVector3(Vector3 vector3)
|
||||
|
||||
{
|
||||
|
||||
x = vector3.x;
|
||||
|
||||
y = vector3.y;
|
||||
|
||||
z = vector3.z;
|
||||
|
||||
}
|
||||
|
||||
public Vector3 ToVector3()
|
||||
|
||||
{
|
||||
|
||||
return new Vector3(x, y, z);
|
||||
|
||||
}
|
||||
|
||||
public Vector2 ToVector2Int()
|
||||
|
||||
{
|
||||
|
||||
return new Vector2((int)x, (int)y);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
这个类可以帮助我们序列化vector3.
|
||||
在想好了上述内容之后,我们创建MapLayout,他的代码如下
|
||||
|
||||
```
|
||||
public class MapLayoutSO : ScriptableObject
|
||||
|
||||
{
|
||||
|
||||
//创建两个列表,一个储存房间信息,一个储存连线信息
|
||||
|
||||
public List<MapRoomData> roomDataList = new List<MapRoomData>();
|
||||
|
||||
public List<MapLineData> lineDataList = new List<MapLineData>();
|
||||
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
|
||||
public class MapRoomData
|
||||
|
||||
{
|
||||
|
||||
//我们要保存的房间的信息
|
||||
|
||||
public float PosX, PosY;
|
||||
|
||||
public int column, line;
|
||||
|
||||
public RoomDataSO roomData;
|
||||
|
||||
public RoomState roomState;
|
||||
|
||||
public List<Vector2Int> linkTo;
|
||||
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
|
||||
public class MapLineData
|
||||
|
||||
{
|
||||
|
||||
//连线我们只需要知道他的起始坐标和结束坐标但是这些内容是vector3类型的
|
||||
|
||||
// vector3类型的无法被序列化,我们需要创建一个可以帮助v3序列化的工具类
|
||||
|
||||
public serializeVector3 startPos, endPos;
|
||||
|
||||
}
|
||||
```
|
||||
那么后面我们需要做的,是从rooms这个列表当中,循环读取,将他的内容储存到我们的SO文件当中.
|
||||
同时在创建地图的时候增加一层判断,如过我们的SO文件当中有数据,那么读取SO文件的数据,而不是从新创建地图.
|
||||
@@ -0,0 +1,8 @@
|
||||
1.连线信息起始坐标没有被正确保存序列化
|
||||
serializeVector3类不能继承MonoBehaviour.这会导致序列化完全失效.
|
||||
- MonoBehaviour 是 Unity 组件的基类,必须依附于 GameObject 存在
|
||||
- ScriptableObject 只能序列化 **纯数据类**(不继承 MonoBehaviour 的类)
|
||||
我由于给serializeVector3继承了MonoBehaviour,导致了我的连线保存以及我的maplayout当中并不能正确的序列化连线的坐标信息导致出现了问题.
|
||||
![[Pasted image 20250222164926.png]]
|
||||
|
||||
![[Pasted image 20250222164957.png]]
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
Reference in New Issue
Block a user