主要技术:同异场景传送,协程,ShaderGraph,TimeLine切换动画

传送门

传送门:同场景传送 不同场景传送

  1. 可传送状态(Player 碰撞传送门触发)且按下E
  2. 判断 传送场景类型 TransitionType { SameScene, DifferentScene }
  3. 遍历该场景内的终点,选择对应终点 destinationTag
  4. 通过协程传送 destinationTag 位置给 Player 位置 SetPositionAndRotation

Shader Graph 创造传送门

  • 创建Shader Graph

    • URP 创建

      QQ截图20220813164736

    • 右键QUAD平面显示

      QQ截图20220813165011

    • Shader设置

      QQ截图20220813172212

    • 创建Shader保存,创建Material 挂载 shader

同场景传送

  • 传送点
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
# TransitionPoint
// 传送门 传送当前场景或其他场景 其他场景判断哪一个终点 E键传送
public class TransitionPoint : MonoBehaviour
{
// 枚举同场景和异场景
public enum TransitionType { SameScene, DifferentScene };

[Header("Transition Info")]
public string sceneName; // 场景名字
public TransitionType transitionType; // 场景变量
public TransitionDestination.DestinationTag destinationTag; // 生成终点变量
// 触发进行
private bool canTrans;

void Update()
{
// 按下E键且在可以传送的状态
if (Input.GetKeyDown(KeyCode.E) && canTrans)
{
// SceneControllor传送
SceneControllor.Instance.TransitionToDestination(this);
}
}
// OnTriggerEnter 在触碰瞬间发生 stay可以一直触发
void OnTriggerStay(Collider other)
{
if (other.CompareTag("Player"))
canTrans = true;
}
// player离开不能传送
void OnTriggerExit(Collider other)
{
if (other.CompareTag("Player"))
canTrans = false;
}
}
  • 传送点枚举值
1
2
3
4
5
6
7
8
# TransitionPoint
public class TransitionDestination : MonoBehaviour
{
// 创建枚举之后生成变量
public enum DestinationTag { ENTER, A, B, C };
public DestinationTag destinationTag;
}

  • 场景控制
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
# SceneControllor
public class SceneControllor : Singleton<SceneControllor>
{
GameObject player;
NavMeshAgent playAgent;

// 异步加载 先加载好再启用
public void TransitionToDestination(TransitionPoint transitionPoint)
{
switch (transitionPoint.transitionType)
{
// 同场景
case TransitionPoint.TransitionType.SameScene:
// 开始协程
StartCoroutine(Transition(SceneManager.GetActiveScene().name, transitionPoint.destinationTag));
break;
// 不同场景
case TransitionPoint.TransitionType.DifferentScene:
break;
}
}
// 协程:传入 传送场景名字和终点tag(坐标)
IEnumerator Transition(string sceneName, TransitionDestination.DestinationTag destinationTag)
{
player = GameManager.Instance.playStats.gameObject;
playAgent = player.GetComponent<NavMeshAgent>();
playAgent.enabled = false;
player.transform.SetPositionAndRotation(GetDestination(destinationTag).transform.position, GetDestination(destinationTag).transform.rotation);
playAgent.enabled = true;
yield return null;
}
// 获得终点tag position和rotation
private TransitionDestination GetDestination(TransitionDestination.DestinationTag destinationTag)
{
var entrances = FindObjectsOfType<TransitionDestination>(); // 注意是FindObjectsOfType
for (int i = 0; i < entrances.Length; i++)
{
if (entrances[i].destinationTag == destinationTag)
return entrances[i];
}
return null;
}
}
  • 鼠标修改
1
2
3
4
5
# MouseManager
if (hitInfo.collider.gameObject.CompareTag("Portal")) // 传送门
{
OnMouseClicked?.Invoke(hitInfo.point);
}

不同场景传送

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
# SceneControllor
public class SceneControllor : Singleton<SceneControllor>
{
GameObject player;
NavMeshAgent playAgent;
public GameObject playerPrefab;
// 重新加载场景不销毁
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(this);
}
// 异步加载 先加载好再启用
public void TransitionToDestination(TransitionPoint transitionPoint)
{
switch (transitionPoint.transitionType)
{
case TransitionPoint.TransitionType.SameScene:
// 开始协程
StartCoroutine(Transition(SceneManager.GetActiveScene().name, transitionPoint.destinationTag));
break;
case TransitionPoint.TransitionType.DifferentScene:
StartCoroutine(Transition(transitionPoint.sceneName, transitionPoint.destinationTag));
break;
}
}
// 协程,传入传送场景名和终点编号
IEnumerator Transition(string sceneName, TransitionDestination.DestinationTag destinationTag)
{
// 保存数据
// 目标场景不是当前场景 跨场景
if (SceneManager.GetActiveScene().name != sceneName)
{
// 加载第二场景 yield return 在这一帧是否需要等待某事件完成 完成执行下面的命令
// 加载场景
yield return SceneManager.LoadSceneAsync(sceneName);
// 人物重新生成
yield return Instantiate(playerPrefab, GetDestination(destinationTag).transform.position, GetDestination(destinationTag).transform.rotation);
yield break;
}
else
{
player = GameManager.Instance.playStats.gameObject;
playAgent = player.GetComponent<NavMeshAgent>();
playAgent.enabled = false;
player.transform.SetPositionAndRotation(GetDestination(destinationTag).transform.position, GetDestination(destinationTag).transform.rotation);
playAgent.enabled = true;
yield return null;
}
}
// 获得终点tag position和rotation,遍历当前场景所有tag找到对应的位置
TransitionDestination GetDestination(TransitionDestination.DestinationTag destinationTag)
{
// 先找到 TransitionDestination 类型,再遍历里面的tag
var entrances = FindObjectsOfType<TransitionDestination>(); // 注意是FindObjectsOfType
for (int i = 0; i < entrances.Length; i++)
{
if (entrances[i].destinationTag == destinationTag)
return entrances[i];
}
return null;
}
}
  • 鼠标事件销毁增加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# PlayerController
// 生成人物鼠标才开始注册方法
void OnEnable()
{
MouseManager.Instance.OnMouseClicked += MoveToTarget; // 添加一个方法 点击地面
MouseManager.Instance.OnEnemyClicked += EventAttack; // 点击敌人
}
void Start()
{
GameManager.Instance.RigisterPlay(characterStates); // 注册 GameManager
}
// 消失时删除方法
void OnDisable()
{
if (!MouseManager.IsInitialized) return;
MouseManager.Instance.OnMouseClicked -= MoveToTarget;
MouseManager.Instance.OnEnemyClicked -= EventAttack;
}

SceneFader 场景转换

TimeLine

  • 新建 TimeLine

    Windows -> Sequencing -> TimeLine

    新建 TimeLine 拖动 Hierarchy 中的 GameObject 或 Animation,Animation Track

    设置关键帧动画,EventSystem 控制动画期间 UI 不可用

QQ截图20220816183454

  • MainMenu 控制 TimeLine 播放时间
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
# MainMenu
using UnityEngine.Playables; // TimeLine 导演

public class MainMenu : MonoBehaviour
{
PlayableDirector director;

private void Awake()
{
// Time Line
director = FindObjectOfType<PlayableDirector>();
director.stopped += NewGame;
}

void PlayTimeLine()
{
// 播放过场动画
director.Play();
}

void NewGame(PlayableDirector obj)
{
// 清除所有记录
PlayerPrefs.DeleteAll();
// 转换场景
SceneControllor.Instance.TransitionToFirstLevel();
}
}
  • CanvasGroup 控制 image 透明度

QQ截图20220816184901

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
# SceneFader
public class SceneFader : MonoBehaviour
{
CanvasGroup canvasGroup;

public float fadeInDuration;
public float fadeOutDuration;

void Awake()
{
canvasGroup = GetComponent<CanvasGroup>();
DontDestroyOnLoad(gameObject);
}
// 淡入淡出
public IEnumerator FadeOutIn()
{
yield return FadeOut(fadeOutDuration); // 协程套协程
yield return FadeIn(fadeOutDuration);
}
// 伴随其他事件完成 协程 淡出
public IEnumerator FadeOut(float time)
{
while(canvasGroup.alpha < 1)
{
canvasGroup.alpha += Time.deltaTime / time; // 0 变成 1
yield return null;
}
}
// 伴随其他事件完成 协程 淡入
public IEnumerator FadeIn(float time)
{
while (canvasGroup.alpha != 0)
{
canvasGroup.alpha -= Time.deltaTime / time; // 1 变成 0
yield return null;
}
Destroy(gameObject);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# SceneControllor // 主要控制场景切换
IEnumerator LoadLevel(string scene)
{
// 生成fade
SceneFader fade = Instantiate(sceneFaderPrefab);
if (scene != "")
{
yield return StartCoroutine(fade.FadeOut(2.5f)); // 渐隐
yield return SceneManager.LoadSceneAsync(scene);
yield return player = Instantiate(playerPrefab, GameManager.Instance.GetEntrance().position, GameManager.Instance.GetEntrance().rotation);
// 保存数据
SaveManager.Instance.SavePlayerData();
yield return StartCoroutine(fade.FadeIn(2.5f)); // 渐出
yield break;
}
}

角色死亡切换主菜单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# SceneControllor
void Start()
{
GameManager.Instance.AddObserver(this); // 注册观察者
fadeFinished = true; // 避免死亡状态一直调用协程
}
// 玩家死亡 加载回主菜单
IEnumerator LoadMain()
{
SceneFader fade = Instantiate(sceneFaderPrefab); // 生成fade
yield return StartCoroutine(fade.FadeOut(2.5f));
yield return SceneManager.LoadSceneAsync("Menu");
yield return StartCoroutine(fade.FadeIn(2.5f));
yield break;
}
// 人物死亡广播 切回主菜单
public void EndNotify()
{
if (fadeFinished)
{
fadeFinished = false;
StartCoroutine(LoadMain());
}
}

Reference:【Unity2020 3DRPG游戏开发教程|Core核心功能01:Create Project 创建项目导入素材|Unity中文课堂】 https://www.bilibili.com/video/BV1rf4y1k7vE?share_source=copy_web&vd_source=d83ce8fdf322bae01b4efc0e4798d0b9