第二场景绘制
设置场景Manager
每次创建新的场景后,都需要先在File->Builder Settings
中将所有场景添加进入列表
- 编写场景转化代码
TransitionManager.cs
**思路:**该部分代码主要实现基础的对场景的异步加载,使用协程的方式完成主要的加载函数,然后使用其他函数对该部分内容进行调用
而具体调用的函数主要使用事件中心来触发,以减少单例模式的使用
在场景中设置一个专门的传送点点GO,该GO将传递两个内容,分别是要激活的场景名称和人物生成的坐标,然后调用传送函数即可
实现场景加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
/// <summary>
/// 实现场景的转换
/// </summary>
/// <param name="sceneName">加载的场景名称</param>
/// <param name="transPoint">传送点</param>
public IEnumerator TransitionScene(string sceneName, Vector3 transPoint)
{
/* 先卸载当前场景,并使用协程加载当前场景 */
yield return SceneManager.UnloadSceneAsync(SceneManager.GetActiveScene());
yield return LoadSceneSetActive(sceneName);
// todo 将人物加载到传送点
}
/// <summary>
/// 使用协程激活场景
/// </summary>
/// <param name="sceneName">需要被激活的场景名称</param>
/// <returns></returns>
public IEnumerator LoadSceneSetActive(string sceneName)
{
/* 使用协程完成场景加载,并激活场景 */
yield return SceneManager.LoadSceneAsync(sceneName, LoadSceneMode.Additive); // 由于场景有多个UI和具体场景,因此需要使用叠加的方式
Scene currentScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
SceneManager.SetActiveScene(currentScene);
}
|
然后通过事件中心对 协程进行调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
private void OnEnable()
{
EventHandler.TransitionEvenet += OnTransitionEvent;
}
private void OnDisable()
{
EventHandler.TransitionEvenet -= OnTransitionEvent;
}
private void OnTransitionEvent(string sceneName, Vector3 transPoint)
{
StartCoroutine(TransitionScene(sceneName, transPoint));
}
|
而每个传送点的代码则是直接对事件中心进行调用
注意每次游戏初期都会加载某一个场景,因此游戏初期需要将所有场景unload,否则导致同时出现两个被加载的场景,在触发场景切换的时候会多次触发。
完善场景切换的物体切换
由于之前代码中很多内容是在场景建立初期的start
中写的,而场景的加载由于会删除场景的物品,因此会导致错误。
为此需要通过设置两个事件函数BeforeSceneUnloadEvent
和AfterSceneloadEvenet
两个事件,然后将场景切换会影响的代码需要在注册事件的函数中编写,每次切换场景的时候调用整个事件。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/* 设置场景切换前后的激活事件 */
public static event Action BeforeSceneUnloadEvent;
public static event Action AfterSceneLoadEvent;
public static void CallBeforeSceneUnloadEvent()
{
BeforeSceneUnloadEvent?.Invoke();
}
public static void CallAfterSceneLoadEvent()
{
AfterSceneLoadEvent?.Invoke();
}
|
增加场景修改位置:
1
2
3
4
5
6
7
8
9
10
|
// switchBounds.cs
private void OnEnable()
{
EventHandler.BeforeSceneUnloadEvent += SwitchConfinerShape;
}
private void OnDisable()
{
EventHandler.BeforeSceneUnloadEvent -= SwitchConfinerShape;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
// inventoryUI.cs
private void OnEnable()
{
EventHandler.UpdateInventoryUI += OnUpdateInventoryUI;
EventHandler.BeforeSceneUnloadEvent += OnBeforeSceneUnloadEvent;
}
private void OnDisable()
{
EventHandler.UpdateInventoryUI -= OnUpdateInventoryUI;
EventHandler.BeforeSceneUnloadEvent -= OnBeforeSceneUnloadEvent;
}
private void OnBeforeSceneUnloadEvent()
{
/*切换场景前取消高亮显示*/
UpdateSlotHighlight(-1);
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// itemManager.cs
private void OnEnable()
{
EventHandler.InstantiateItemInScene += OnInstantiateItemInScene;
EventHandler.AfterSceneLoadEvent += OnAfterSceneLoadEvent;
}
private void OnDisable()
{
EventHandler.InstantiateItemInScene -= OnInstantiateItemInScene;
EventHandler.AfterSceneLoadEvent -= OnAfterSceneLoadEvent;
}
private void OnAfterSceneLoadEvent()
{
_itemParent = GameObject.FindGameObjectWithTag("ItemParent");
}
|
学习编写特性,并将其设置为下拉菜单
Unity的特性Attribute
可以将Unity界面显示的变量(Property)修改为我们期望的样子,比如将手写的string输入框设置为下拉菜单的模式。
而具体的做法是将需要修改的变量property,在前方增加描述[Attribute],然后编写一个用于描述该Attribute
的代码SceneNameAttribute
,其继承自PropertyAttribute
;然后编写一个用于绘制对应GUI的代码SceneNameDrawer
,该代码继承自PropertyDrawer
,用于描述该特性怎么描述GUI。
注意这两部分代码不能放在原本绘制UI toolkit的位置,否则不能使用,因此放在Utility的位置
1
2
3
4
5
6
7
|
// SceneNameAttribute的写法,只需要明确有这个特性
using UnityEngine;
public class SceneNameAttribute : PropertyAttribute
{
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
|
// SceneNameDrawer.cs ,需要将GUI的内容都确定下来,并对其进行赋值
using System;
using UnityEditor;
using UnityEngine;
[CustomPropertyDrawer(typeof(SceneNameAttribute))]
public class SceneNameDrawer: PropertyDrawer
{
private int sceneIndex = -1; // 当前选取的场景编号
private string[] _sceneNameSplits = { "/", ".unity" }; // 由于BuildingSetting中场景有路径,因此需要被删除一部分
private GUIContent[] _sceneNames; // 显示在GUI上的文本内容
public override void OnGUI(Rect position, SerializedProperty property, GUIContent label)
{
/*如果场景数为0,则不再绘制*/
if (EditorBuildSettings.scenes.Length == 0) return;
/*从buildSetting中找出所有的场景名称*/
if (sceneIndex == -1)
GetSceneNameArray(property);
int oldIndex = sceneIndex;
/*绘制GUI,并设置GUI点击的内容为我们的目标值*/
// Popup参数分别是绘制的位置,显示的载体,要显示的内容的需要,下拉菜单的具体内容
sceneIndex = EditorGUI.Popup(position, label, sceneIndex, _sceneNames);
if (oldIndex != sceneIndex)
property.stringValue = _sceneNames[sceneIndex].text;
}
private void GetSceneNameArray(SerializedProperty property)
{
// 获得当前BuildingSetting中所有的场景数据
var scenes = EditorBuildSettings.scenes;
/*对获取场景数据的函数进行初始化*/
_sceneNames = new GUIContent[scenes.Length];
/*通过循环将场景名称放入GUI的数组内容中*/
for (int i = 0; i < scenes.Length; i++)
{
string path = scenes[i].path;
string[] scenePath = path.Split(_sceneNameSplits, StringSplitOptions.RemoveEmptyEntries);
string sceneName = String.Empty;
if (scenePath.Length > 0)
{
sceneName = scenePath[scenePath.Length - 1];
}
else
{
sceneName = "(Delete Scene)";
}
_sceneNames[i] = new GUIContent(sceneName);
}
if (_sceneNames.Length == 0)
_sceneNames = new[] { new GUIContent("Check your Build Settings") };
/*对property中的内容进行赋值,首先需要确保如果内容不为空,那么其必须是我们设置的场景名称*/
if (!string.IsNullOrEmpty(property.stringValue))
{
bool nameFound = false;
for (int i = 0; i < _sceneNames.Length; i++)
{
if (property.stringValue == _sceneNames[i].text)
{
sceneIndex = i;
nameFound = true;
break;
}
}
if (!nameFound)
{
sceneIndex = 0;
}
}
else
{
sceneIndex = 0;
}
/*用刚刚获得sceneIndex对应的场景名,对property进行赋值*/
property.stringValue = _sceneNames[sceneIndex].text;
}
}
|
过渡动画
思路:通过设置一个新的Canvas,增加一个覆盖层,通过对该覆层的透明度进行修改来实现过渡动画,该覆盖层可以通过设置CanvasGroup
组件来保证其子物体也能一同进行淡入淡出的过渡。
而具体的执行函数将在TransitionManager.cs
中通过协程进行完成。
注意:为了保存新的覆层能够覆盖整个屏幕,需要将该Canvas的排序进行修改
具体做法,首先设置一个Canvas,设置号Canvas相关的属性后,屏幕尺寸和排序(必须),然后在Canvas下创建一个Panel
,即该Panel
就是一个覆层。然后对该Panel
设置对应的CanvasGroup
以保证画布中的内容能一同渐变,然后通过的代码对整个Canvas进行控制即可。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
/// <summary>
/// 场景渐变
/// </summary>
/// <param name="targetAlpha">1为全覆盖,0为不覆盖</param>
/// <returns></returns>
public IEnumerator SceneFader(float targetAlpha)
{
isFading = true;
sceneFaderCanvasGroup.blocksRaycasts = true; // 当前图片不能点击
/*使用数学方法计算场景的渐变过程速度*/
float speed = Mathf.Abs(targetAlpha - sceneFaderCanvasGroup.alpha) / Settings.SceneFaderDuration;
while (!Mathf.Approximately(sceneFaderCanvasGroup.alpha, targetAlpha))
{
// 如果没达到目标,则不断将当前的透明度向目标移动
sceneFaderCanvasGroup.alpha = Mathf.MoveTowards(sceneFaderCanvasGroup.alpha,
targetAlpha, speed * Time.deltaTime);
yield return null;
}
isFading = false;
sceneFaderCanvasGroup.blocksRaycasts = false;
}
|
其中isFading
用于标志只用当渐变结束,才能移动目标
保存和加载场景物品
思路:每次卸载场景的时候,用一个内容记录场景中所有物体的内容和坐标,然后加载场景的时候读取并生成即可。因此使用一个哈希表用于存储相关内容,key为场景名称,value为需要存储数据的列表。
注意在生成数据的时候,需要先将场景中原有的数据都删除,防止重复生成。
由于Vector3在一个类中不能被序列化,因此需要设计一个专门的类用来描述Vector3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
|
// 在dataCollection.cs中描述保存Item位置的变量
/// <summary>
/// 定义一个能被Unity识别的Vector3
/// </summary>
[System.Serializable]
public class SerializableVector3
{
public float x, y, z;
public SerializableVector3(Vector3 pos)
{
this.x = pos.x;
this.y = pos.y;
this.z = pos.z;
}
public Vector3 ToVector3()
{
return new Vector3(x, y, z);
}
/*获得瓦片地图的位置*/
public Vector2 ToVector2()
{
return new Vector2((int)x, (int)y);
}
}
/// <summary>
/// 定义场景中item的描述类
/// </summary>
[System.Serializable]
public class SceneItem
{
public int itemId;
public SerializableVector3 itemPos;
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
|
/// <summary>
/// 将场景中所有item存储进入字典中
/// </summary>
private void GetAllSceneItem()
{
/*获取当前场景的所有item*/
List<SceneItem> currentItems = new List<SceneItem>();
foreach (var item in FindObjectsOfType<Item>())
{
SceneItem sceneItem = new SceneItem
{
itemId = item.itemID,
itemPos = new SerializableVector3(item.transform.position)
};
currentItems.Add(sceneItem);
}
/*将场景中所有的item存放在对应的dict中*/
if (_sceneItemDict.ContainsKey(SceneManager.GetActiveScene().name))
{
_sceneItemDict[SceneManager.GetActiveScene().name] = currentItems;
}
else
{
_sceneItemDict.Add(SceneManager.GetActiveScene().name, currentItems);
}
}
/// <summary>
/// 重建当前场景中的所有item
/// </summary>
public void ReBuildSceneItems()
{
List<SceneItem> currentItems = new List<SceneItem>();
/*获取字典中当前场景item的存储*/
if (_sceneItemDict.TryGetValue(SceneManager.GetActiveScene().name, out currentItems))
{
if (currentItems != null)
{
// 先将当前场景清场,防止有代码遗漏的物品
foreach (var item in FindObjectsOfType<Item>())
{
Destroy(item.gameObject);
}
// 重建所有内容
foreach (var item in currentItems)
{
Item newItem = Instantiate(itemPrefab, item.itemPos.ToVector3(),
Quaternion.identity, _itemParent.transform);
newItem.InitItem(item.itemId);
}
}
}
}
|