麦田物语第十七天

系列文章目录

麦田物语第十七天



一、创建 TransitionManager 控制人物场景切换

本节课想要实现场景切换的功能,因为当我们在每次创建新的场景之后,需要打开File->Build Setting选项将新创建的场景添加进去,这一步很重要的嘞。

在下面编写的TransitionManager和Teleport脚本都要添加到MFarm.Transition的命名空间下,避免其他脚本的调用

那么要实现跳转场景的功能,首先我们需要在PersistentScene场景中添加空物体TransitionManager,同时创建Scripts->Transition->TransitionManager脚本,将该脚本添加到空物体上;利用这个脚本我们可以控制角色所在的场景(将场景设置为Active状态)。
接着进行TransitionManager脚本的编写,首先我们需要声明激活场景名称的变量;然后我们编写一个协程LoadSceneSetActive,该协程的作用是加载场景并将场景激活(在协程中所有的场景加载是异步加载即LoadSceneAsync方法),该方法有很多重载,我们使用通过场景名加载场景,同时由于我们在该游戏中是用的是叠加的方式去切换场景,因此加载场景的模式改为Additive,此时场景加载好了;那么接下来我们需要那个这个场景,该场景通过GetSceneAt方法获取到当前激活场景的最后一个,取到的就是我们加载的那个场景啦!!!(当时理解了半天呢)之后我们便将加载的场景激活就好了,最后我们需要在Start方法中开启这个协程,同时返回Unity对变量字段进行赋值,UnLoad两个场景,运行游戏即可。(游戏开始时由于找不到ItemParent和Bounds,所以会报空,这个之后会解决滴)

协程方法如果需要在其他方法中调用的话,那么需要使用StartCoroutine方法调用,但是在协程中调用协程的话,直接调用即可。

当然,只有这个协程肯定是不够的,因为我们还要进行场景的切换呢,那么我们还需要编写一个新的协程Transition,首先在这个协程中我们要将卸载当前激活的场景(使用UnLoadSceneAsync异步加载),通过当前场景的序号或者名字卸载掉这个场景;然后调用激活场景的写成即可(该协程还需要场景名和目标位置两个参数)。
现在转换的方法也写好了,那么怎么触发这个方法呢,没错,就是在EventHandler编写事件,在角色碰撞到碰撞体后发送消息,同时在TransitionManager脚本中编写收到消息之后的方法,也就是开启Transition这个协程了。
那么下面我们就来详细介绍一下吧!
我们需要先编写事件,在EventHandler脚本中添加如下代码:

EventHandler脚本中传送角色跳转场景和所在位置的代码如下

//传送主角时将场景名称和主角生成位置传送给TransitionManager脚本
    public static event Action<string, Vector3> TransitionEvent;
    public static void CallTransitionEvent(string sceneName,Vector3 pos)
    {
        TransitionEvent?.Invoke(sceneName, pos);
    }

接着我们需要创建一个空物体,为其添加Box Collider 2D,并勾选Is Triggeer属性,调整其位置和大小。然后创建Scripts->Transition->Teleport脚本,并将改脚本添加到到碰撞物体上。这个脚本所要做的事情已经很明显了,就是在OnTriggerEnter2D脚本中将该脚本中声明的变量跳转场景的名称和跳转之后的目标位置发送给TransitionManager脚本并调用跳转场景的协程就好了。(记得返回Unity将变量进行赋值,同时将Player添加标签Player)

Teleport脚本代码如下

namespace MFarm.Transition
{
    public class Teleport : MonoBehaviour
    {
        public string sceneToGo;
        public Vector3 positionToGo;

        private void OnTriggerEnter2D(Collider2D collision)
        {
            if (collision.CompareTag("Player"))
            {
                EventHandler.CallTransitionEvent(sceneToGo, positionToGo);
            }
        }
    }
}

最后我们要做的就是在TransitionManager脚本中添加注册事件,并编写OnTransitionEvent,只是开启协程就可以了,我们可以返回Unity测试一下,但是记得将所有的场景(01,02)给关闭了。

二、实现人物跨场景移动以及场景加载前后事件

在该节课中我们需要解决上节课的报错以及问题,并且实现从场景1跳转场景2的功能(该节课恢复系很多之前的代码)。
由于我们在切换场景时会找不到摄像机边界以及ItemParent,出现报空的情况,;这种情况怎么解决呢,我们应该在跳转场景后再执行寻找摄像机边界等物体的方法就行了,同时需要使用呼叫的方式来告诉部分脚本在跳转场景前和跳转场景后的事件,同时我们还要给角色发送呼叫,告诉其跳转场景之后的位置,这个也是需要重新编写事件来实现的。

EventHandler脚本中跳转场景前,跳转场景后以及传送角色位置的事件如下:

	//在切换场景之前呼叫这个事件,告诉其他的脚本保存数据,包括位置之类的
    public static event Action BeforeSceneUnloadEvent;
    public static void CallBeforeSceneUnloadEvent()
    {
        BeforeSceneUnloadEvent?.Invoke();
    }

    //加载场景之后的事件
    public static event Action AfterSceneLoadedEvent;
    public static void CallAfterSceneLoadedEvent()
    {
        AfterSceneLoadedEvent?.Invoke();
    }

    //移动人物坐标???
    public static event Action<Vector3> MoveToPosition;
    public static void CallMoveToPosition(Vector3 targetPosition)
    {
        MoveToPosition?.Invoke(targetPosition);
    }

接着我们回到Transition脚本的Transition协程中,在卸载场景之前呼叫BeforeSceneUnloadEvent事件,在加载场景之后呼叫AfterSceneLoadedEvent事件。
然后我们回到SwitchBounds脚本中,编写场景加载后事件的方法并直接调用SwitchConfinerShape方法。再接着返回ItemManager脚本中,编写场景加载后事件的方法,将Start方法中的代码(itemParent = GameObject.FindWithTag(“ItemParent”).transform;)放入OnAfterSceneLoadedEvent方法中,这样就可以解决报错了。

SwitchBounds脚本代码如下:

public class SwitchBounds : MonoBehaviour
{
    //T0D0:切换场景后更改调用,而不是在Start方法中调用
    //private void Start()
    //{
    //    SwitchConfinerShape();
    //}

    private void OnEnable()
    {
        EventHandler.AfterSceneLoadedEvent += SwitchConfinerShape;
    }

    private void OnDisable()
    {
        EventHandler.AfterSceneLoadedEvent -= SwitchConfinerShape;
    }

    private void SwitchConfinerShape()
    {
        PolygonCollider2D confinerShape = GameObject.FindGameObjectWithTag("BoundsConfiner").GetComponent<PolygonCollider2D>();

        CinemachineConfiner confiner = GetComponent<CinemachineConfiner>();

        confiner.m_BoundingShape2D = confinerShape;

        //Call this if the bounding shape's points change at runtime
        confiner.InvalidatePathCache();
    }
}

ItemManager脚本代码如下:

namespace MFarm.Inventory
{
    public class ItemManager : MonoBehaviour
    {
        public Item itemPrefab;
        private Transform itemParent;

        private void OnEnable()
        {
            EventHandler.InstantiateItemInScene += OnInstantiateItemInScene;
            EventHandler.AfterSceneLoadedEvent += OnAfterSceneLoadedEvent;
        }

        

        private void OnDisable()
        {
            EventHandler.InstantiateItemInScene -= OnInstantiateItemInScene;
            EventHandler.AfterSceneLoadedEvent -= OnAfterSceneLoadedEvent;
        }

        private void OnAfterSceneLoadedEvent()
        {
            itemParent = GameObject.FindWithTag("ItemParent").transform;
        }

        

        private void OnInstantiateItemInScene(int ID, Vector3 pos)
        {
            var item = Instantiate(itemPrefab, pos, Quaternion.identity, itemParent);
            item.itemID = ID;
        }
    }
}

但是还存在一些问题。
其一就是为了保证人物在加载场景时不会出现移动,同时将人物的位置传过去。(当加载场景前将角色设置为不可移动状态,加载场景完成后将角色设置为可移动状态,同时在通过传送角色位置的事件设置玩家的位置),因此我们要打开Player的脚本,设置bool类型的变量inputDisable,当其为false时,玩家才可以操纵角色,我们只需要编写相应的函数,在跳转场景前将inputDisable设置为true,跳转场景后将inputDisable设置为false,同时编写函数将角色的位置设置为目标位置即可。

Player脚本中新增的代码如下

public class Player : MonoBehaviour
{
    private Rigidbody2D rb;
    public float speed;
    private float inputX;
    private float inputY;
    private Vector2 movementInput;
    [Header("Player移动动画")]
    private Animator[] animators;
    private bool isMoving;

    private bool inputDisable;

    private void Awake()
    {
        rb = GetComponent<Rigidbody2D>();
        animators = rb.GetComponentsInChildren<Animator>();
    }

    private void OnEnable()
    {
        EventHandler.BeforeSceneUnloadEvent += OnBeforeSceneUnLoadEvent;
        EventHandler.AfterSceneLoadedEvent += OnAfterSceneLoadedEvent;
        EventHandler.MoveToPosition += OnMoveToPosition;
    }

    

    private void OnDisable()
    {
        EventHandler.BeforeSceneUnloadEvent -= OnBeforeSceneUnLoadEvent;
        EventHandler.AfterSceneLoadedEvent -= OnAfterSceneLoadedEvent;
        EventHandler.MoveToPosition -= OnMoveToPosition;
    }

    private void OnMoveToPosition(Vector3 targetPosition)
    {
        transform.position = targetPosition;
    }

    private void OnAfterSceneLoadedEvent()
    {
        inputDisable = false;
    }

    private void OnBeforeSceneUnLoadEvent()
    {
        inputDisable = true;
    }

    private void Update()
    {
        if (inputDisable == false)
            PlayerInput();
        SwitchAnimation();
    }
}

其二就是当玩家选中物体跳转场景时,我们应该将玩家设置为正常行走状态,并将被选中的SlotUI设置为未被选中状态。
首先我们返回AnimatorOverride脚本中,在这个脚本中我们实现了玩家动作的切换以及玩家举起物品图标的切换,在这个场景中我们也是编写切换场景前事件的函数(同时在OnEnable和OnDisable方法中添加函数),在OnBeforeSceneUnLoadEvent方法中实现上述的两个功能。
然后返回InventoryUI脚本,在这个脚本中我们实现了SlotUI的选择等功能,和上面一样,也是在卸载场景前的事件编写函数,在UpdateSlotHightlight方法中传递参数为-1代表取消高亮显示。

AnimatorOverride脚本的代码如下

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class AnimatorOverride : MonoBehaviour
{
    private Animator[] animators;
    public SpriteRenderer holdItem;

    [Header("各部分动画列表")]
    public List<AnimatorType> animatorTypes;

    private Dictionary<string, Animator> animatorNameDict = new Dictionary<string, Animator>();

    private void Awake()
    {
        animators = GetComponentsInChildren<Animator>();

        foreach (var anim in animators)
        {
            animatorNameDict.Add(anim.name, anim);
        }
    }

    private void OnEnable()
    {
        EventHandler.ItemSelectedEvent += OnItemSelectedEvent;
        EventHandler.BeforeSceneUnloadEvent += OnBeforeSceneUnLoadEvent;
    }

    

    private void OnDisable()
    {
        EventHandler.ItemSelectedEvent -= OnItemSelectedEvent;
        EventHandler.BeforeSceneUnloadEvent -= OnBeforeSceneUnLoadEvent;
    }

    private void OnBeforeSceneUnLoadEvent()
    {
        holdItem.enabled = false;
        SwitchAnimator(PartType.None);
    }

    private void OnItemSelectedEvent(ItemDetails itemDetails, bool isSelected)
    {
        //WORKFLOW:不同的工具返回不同的动画在这里补全
        //语法糖
        PartType currentType = itemDetails.itemType switch
        {
            ItemType.Seed => PartType.Carry,
            ItemType.Commodity => PartType.Carry,
            _=> PartType.None
        };

        if (isSelected == false)
        {
            currentType = PartType.None;
            holdItem.enabled = false;
        }
        else
        {
            if (currentType == PartType.Carry)
            {
                holdItem.sprite = itemDetails.itemOnWorldSprite;
                holdItem.enabled = true;
            }
        }

        SwitchAnimator(currentType);
    }

    private void SwitchAnimator(PartType partType)
    {
        foreach(var item in animatorTypes)
        {
            if (item.partType == partType)
            {
                animatorNameDict[item.partName.ToString()].runtimeAnimatorController = item.overrideController;
            }
        }
    }
}

InventoryUI脚本的新增代码如下:

using MFarm.Inventory;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;


namespace MFram.Inventory
{
    public class InventoryUI : MonoBehaviour
    {
        public ItemTooltip itemTooltip;

        [Header("拖拽图片")]
        public Image dragItem;


        [Header("玩家背包UI")]
        [SerializeField] private GameObject bagUI;
        private bool bagOpened;

        [SerializeField] private SlotUI[] playerSlots;


        private void OnEnable()
        {
            EventHandler.UpdateInventoryUI += OnUpdateInventoryUI;
            EventHandler.BeforeSceneUnloadEvent += OnBeforeSceneUnloadEvent;
        }

        

        private void OnDisable()
        {
            EventHandler.UpdateInventoryUI -= OnUpdateInventoryUI;
            EventHandler.BeforeSceneUnloadEvent -= OnBeforeSceneUnloadEvent;
        }

        private void OnBeforeSceneUnloadEvent()
        {
            UpdateSlotHightlight(-1);
        }

        /// <summary>
        /// 更新Slot高亮显示
        /// </summary>
        /// <param name="index">Slot序号</param>
        public void UpdateSlotHightlight(int index)
        {
            foreach (var slot in playerSlots)
            {
                if (slot.isSelected && slot.slotIndex == index)
                {
                    slot.slotHighlight.gameObject.SetActive(true);
                }
                else
                {
                    slot.isSelected = false;
                    slot.slotHighlight.gameObject.SetActive(false);
                }
            }
        }

    }
}

最后我们要完成本节课的作业:就是实现玩家从场景1返回场景2,其实很简单,就是在们的位置添加碰撞体,然后挂载脚本Teleport即可(切记对其赋初值)。
今天的学习就到这了!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值