【制作100个unity游戏之26】unity2d横版卷轴动作类游11(附带项目源码)

简介: 【制作100个unity游戏之26】unity2d横版卷轴动作类游11(附带项目源码)

前言

欢迎来到【制作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即可

效果

image.png

获取虚拟摄像机边界

我们需要加载场景再获取虚拟摄像机边界,所以之前的方法就已经行不通了

修改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);
          }
      };
     //...
}

配置

效果

源码

源码不出意外的话我会放在最后一节

目录
相关文章
|
3天前
|
存储 JSON 关系型数据库
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
12 2
|
3天前
|
图形学
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)(上)
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)
10 2
|
3天前
|
存储 JSON 图形学
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
6 0
|
3天前
|
图形学
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)(下)
【制作100个unity游戏之29】使用unity复刻经典游戏《愤怒的小鸟》(完结,附带项目源码)(下)
8 0
|
3天前
|
图形学
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版3(附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版3(附带项目源码)
14 2
|
3天前
|
图形学
【制作100个unity游戏之28】花半天时间用unity复刻童年4399经典小游戏《黄金矿工》(附带项目源码)
【制作100个unity游戏之28】花半天时间用unity复刻童年4399经典小游戏《黄金矿工》(附带项目源码)
13 0
|
3天前
|
图形学
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版2(附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版2(附带项目源码)
8 1
|
3天前
|
存储 JSON 关系型数据库
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版13(完结,附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版13(完结,附带项目源码)
9 0
|
3天前
|
图形学
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版12(附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版12(附带项目源码)
9 0
|
3天前
|
存储 图形学
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版11(附带项目源码)
【制作100个unity游戏之27】使用unity复刻经典游戏《植物大战僵尸》,制作属于自己的植物大战僵尸随机版和杂交版11(附带项目源码)
8 0