前言
欢迎来到【制作100个Unity游戏】系列!本系列将引导您一步步学习如何使用Unity开发各种类型的游戏。在这第26篇中,我们将探索如何用unity制作一个unity2d横版卷轴动作类游戏,我会附带项目源码,以便你更好理解它。
本节主要实现场景切换和淡入淡出
TextMeshPro中文字体
创建TextMeshPro字体资源(字体资源大家就自行去下载了,网上有很多)
中文字体,是会根据你当前使用的需要来渲染这些文字,它不会把你的几干几万个汉字提前帮你渲染好,所以你用到什么渲染什么,这就导致如果你字多了,很可能他没有地方渲染了。这时候就需要修改generation settings,如果你发现你的字不够用了,出现方块了,那你就把这个画布调到最大来,尽可能的渲染更多的中文文字
绘制开始界面,把所有的公共部分都移动到这个场景(比如人物 音效管理器 状态栏 摄像机等等)
传送门 场景切换
新增几个不同的场景
这里给几个参考
如何让背景图片进行吸附
按住V键拖拽物体
场景切换
大概的步骤就是:卸载当前场景 加载新的场景 修改player位置。
具体可以参考我之前写的文章:【Unity实战】切换场景加载进度和如何在后台异步加载具有庞大世界的游戏场景,实现无缝衔接(附项目源码)
新增SceneField,这代码通过使用SceneField类和SceneFieldPropertyDrawer属性绘制器,开发者可以在自定义的脚本中方便地引用和管理场景对象,并在Inspector面板中进行编辑和选择操作。这对于需要频繁切换场景或者处理多个场景的情况非常有用。
using UnityEngine; #if UNITY_EDITOR using UnityEditor; #endif [System.Serializable] public class SceneField { [SerializeField] private Object m_SceneAsset; [SerializeField] private string m_SceneName = ""; public string SceneName { get { return m_SceneName; } } // 使其与现有的Unity方法(LoadLevel / LoadScene)兼容 public static implicit operator string(SceneField sceneField) { return sceneField.SceneName; } } #if UNITY_EDITOR [CustomPropertyDrawer(typeof(SceneField))] public class SceneFieldPropertyDrawer : PropertyDrawer { public override void OnGUI(Rect _position, SerializedProperty _property, GUIContent _label) { EditorGUI.BeginProperty(_position, GUIContent.none, _property); SerializedProperty sceneAsset = _property.FindPropertyRelative("m_SceneAsset"); SerializedProperty sceneName = _property.FindPropertyRelative("m_SceneName"); _position = EditorGUI.PrefixLabel(_position, GUIUtility.GetControlID(FocusType.Passive), _label); if (sceneAsset != null) { // 显示场景选择器,让用户选择一个场景 sceneAsset.objectReferenceValue = EditorGUI.ObjectField(_position, sceneAsset.objectReferenceValue, typeof(SceneAsset), false); // 如果已经选择了场景,则将场景名称保存在场景名称变量中 if (sceneAsset.objectReferenceValue != null) { sceneName.stringValue = (sceneAsset.objectReferenceValue as SceneAsset).name; } } EditorGUI.EndProperty(); } } #endif
新增MainMenuManager,控制加载开始界面菜单
public class MainMenuManager : MonoBehaviour { [SerializeField, Header("需要加载的场景数组")] private SceneField[] _scenesToLoad; [SerializeField, Header("需要隐藏的对象")] private GameObject[] _objectsToHide; private void Awake() { LoadScenes(); HideMenu(); } private void Start() { AudioManager.Instance.PlayMusic("菜单界面"); } // 隐藏界面 private void HideMenu() { for (int i = 0; i < _objectsToHide.Length; i++) { _objectsToHide[i].SetActive(false); // 隐藏需要隐藏的对象 } } // 加载场景 private void LoadScenes() { for (int i = 0; i < _scenesToLoad.Length; i++) { bool isSceneLoaded = false; for (int j = 0; j < SceneManager.sceneCount; j++) { Scene loadedScene = SceneManager.GetSceneAt(j); if (loadedScene.name == _scenesToLoad[i].SceneName) { isSceneLoaded = true; break; } } if (!isSceneLoaded) { SceneManager.LoadSceneAsync(_scenesToLoad[i].SceneName, LoadSceneMode.Additive); // 异步加载需要加载的场景 } } } }
配置
运行效果
限制菜单界面不能控制角色
修改PlayerController
public void controlEnable(){ // 启用输入控制 inputControl.Enable(); } // private void OnEnable() // { // // 启用输入控制 // inputControl.Enable(); // } // private void OnDisable() // { // // 禁用输入控制 // inputControl.Disable(); // }
开始游戏和切换场景
新增SceneLoadTrigger,统一处理开始游戏和切换场景
public class SceneLoadTrigger : MonoBehaviour, IInteractable { [SerializeField, Header("需要加载的场景数组")] private SceneField[] _scenesToLoad; [SerializeField, Header("需要卸载的场景数组")] private SceneField[] _scenesToUnload; [SerializeField, Header("需要显示的对象")] private GameObject[] _objectsToShow; [SerializeField, Header("需要隐藏的对象")] private GameObject[] _objectsToHide; public Vector2 playerVector2;// 调整玩家位置 public CameraControl cameraControl; private GameObject _player; // 玩家对象 private void Awake() { _player = GameObject.FindGameObjectWithTag("Player"); // 查找并赋值玩家对象 playerController = _player.GetComponent<PlayerController>(); cameraControl = GameObject.FindGameObjectWithTag("CM").GetComponent<CameraControl>(); fadeImage = GameObject.FindGameObjectWithTag("FadeImage").GetComponent<Image>(); } public void TriggerAction() { Debug.Log("传送"); UnloadScenes(); LoadScenes(); } //开始游戏 public void StartGame() { UnloadScenes(); LoadScenes(); ShowMenu(); HideMenu(); //修改玩家位置 _player.transform.position = playerVector2; AudioManager.Instance.PlayMusic("关卡1"); //启用输入控制 playerController.controlEnable(); } // 加载场景 private void LoadScenes() { int scenesLoadedCount = 0; // 记录已加载的场景数量 for (int i = 0; i < _scenesToLoad.Length; i++) { bool isSceneLoaded = false; for (int j = 0; j < SceneManager.sceneCount; j++) { Scene loadedScene = SceneManager.GetSceneAt(j); if (loadedScene.name == _scenesToLoad[i].SceneName) { isSceneLoaded = true; break; } } if (!isSceneLoaded) { SceneManager.LoadSceneAsync(_scenesToLoad[i].SceneName, LoadSceneMode.Additive).completed += operation => { scenesLoadedCount++; // 加载完成后增加已加载场景数量 if (scenesLoadedCount == _scenesToLoad.Length) { // 所有场景都已加载完成,可以执行相关操作 Debug.Log("所有场景都已加载完成."); } }; } } } // 卸载场景 private void UnloadScenes() { for (int i = 0; i < _scenesToUnload.Length; i++) { for (int j = 0; j < SceneManager.sceneCount; j++) { Scene loadedScene = SceneManager.GetSceneAt(j); if (loadedScene.name == _scenesToUnload[i].SceneName) { SceneManager.UnloadSceneAsync(_scenesToUnload[i].SceneName); // 异步卸载需要卸载的场景 } } } } // 显示界面 private void ShowMenu() { for (int i = 0; i < _objectsToShow.Length; i++) { _objectsToShow[i].SetActive(true); } } // 隐藏界面 private void HideMenu() { for (int i = 0; i < _objectsToHide.Length; i++) { _objectsToHide[i].SetActive(false); } } }
配置
效果
切换到下一个场景
之前复用前面的SceneLoadTrigger即可
效果
获取虚拟摄像机边界
我们需要加载场景再获取虚拟摄像机边界,所以之前的方法就已经行不通了
修改CameraControl,注释调之前的测试方法
// private void Start() // { // GetNewCameraBounds(); // }
修改SceneLoadTrigger
public CameraControl cameraControl; // 加载场景 private void LoadScenes() { //... SceneManager.LoadSceneAsync(_scenesToLoad[i].SceneName, LoadSceneMode.Additive).completed += operation => { scenesLoadedCount++; // 加载完成后增加已加载场景数量 if (scenesLoadedCount == _scenesToLoad.Length) { // 所有场景都已加载完成,可以执行相关操作 Debug.Log("所有场景都已加载完成."); //获取相机边界方法 cameraControl.GetNewCameraBounds(); } }; //... }
配置
效果,可以看到进入新的场景摄像机边界自动绑定上了
使用DoTween实现场景转换的淡入淡出
DoTween的安装和使用可以看我之前的文章:【推荐100个unity插件之2】 DoTween动画插件的安装和使用整合(最全)
新增覆盖全屏的黑图,默认透明的设置为0,记得去除投射勾选
修改SceneLoadTrigger
sing DG.Tweening; public Image fadeImage; // 用于淡入淡出的图片 //开始游戏 public void StartGame() { //界面淡出,图片变黑 fadeImage.DOBlendableColor(Color.black, 1f); //... } // 加载场景 private void LoadScenes() { //... SceneManager.LoadSceneAsync(_scenesToLoad[i].SceneName, LoadSceneMode.Additive).completed += operation => { scenesLoadedCount++; // 加载完成后增加已加载场景数量 if (scenesLoadedCount == _scenesToLoad.Length) { // 所有场景都已加载完成,可以执行相关操作 Debug.Log("所有场景都已加载完成."); //获取相机边界方法 cameraControl.GetNewCameraBounds(); //用于立即完成正在进行的动画 fadeImage.DOComplete(); //界面淡入,图片变透明 fadeImage.DOBlendableColor(Color.clear, 1f); } }; //... }
配置
效果
源码
源码不出意外的话我会放在最后一节