系列文章目录
迷失岛游戏框架开发个人每集总结(第一期)
迷失岛游戏框架开发个人每集总结(第二期)
迷失岛游戏框架开发个人每集总结(第三期)
前言
在上一期中,我们实现了通过鼠标点击带有标签“item”的物体,根据物品的名称在已设置好的,内容为物品的名称及其对应图片的列表里寻找该物品信息,该物体的图片、名称以及将对应的序号传给订阅获取这些信息事件的InventoryUI。根据这些信息对我们的ui进行更新。
这一期要实现的是保存物品在场景中的状态,以及互动的状态。这样在切换场景后就不会出现例如:刚刚在该场景拾取了钥匙,切换场景回来后钥匙依旧在该场景里显示的情况。
1
1.1
ObjectManager 用于管理物品状态
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//用于保存物品以及的状态
//场景第一次被加载进来之后,我要记录有哪些物体来构成我们的字典,并且场景里还有一些互动,在卸载这个场景之前,我要记录它们的状态
public class ObjectManager : MonoBehaviour
{
//创建一个字典,储存物品的名字以及判断该物品是否可用的布尔值
private Dictionary<ItemName, bool> itemAvailableDict = new Dictionary<ItemName, bool>();
private Dictionary<string, bool> interactiveStateDict = new Dictionary<string, bool>();//保存互动状态
//注册事件的方法
private void OnEnable()
{
EventHandler.BeforeSceneUnloadEvent += OnBeforeSceneUnloadEvent;
EventHandler.AfterSceneUnloadEvent += OnAfterSceneUnloadEvent;
EventHandler.updateUIEvent += OnUpdateUIEvent;
}
private void OnDisable()
{
EventHandler.BeforeSceneUnloadEvent -= OnBeforeSceneUnloadEvent;
EventHandler.AfterSceneUnloadEvent -= OnAfterSceneUnloadEvent;
EventHandler.updateUIEvent -= OnUpdateUIEvent;
}
private void OnBeforeSceneUnloadEvent()
{
foreach (var item in FindObjectsOfType<Item>())
{
if (!itemAvailableDict.ContainsKey(item.itemName))
itemAvailableDict.Add(item.itemName, true);//true代表在当前场景中该物体是显示状态
}
foreach (var item in FindObjectsOfType<Interactive>())
{
if (interactiveStateDict.ContainsKey(item.name))
interactiveStateDict[item.name] = item.isDone;
else
interactiveStateDict.Add(item.name, item.isDone);
}
}
private void OnAfterSceneUnloadEvent()
{
//如果已经在字典中则更新显示状态,不在则添加
foreach(var item in FindObjectsOfType<Item>())
{
if (!itemAvailableDict.ContainsKey(item.itemName))
itemAvailableDict.Add(item.itemName, true);
else
item.gameObject.SetActive(itemAvailableDict[item.itemName]);
}
foreach (var item in FindObjectsOfType<Interactive>())
{
if (interactiveStateDict.ContainsKey(item.name))
item.isDone=interactiveStateDict[item.name] ;
else
interactiveStateDict.Add(item.name, item.isDone);
}
}
//只在拾取物品后更新
private void OnUpdateUIEvent(ItemDetails itemDetails ,int arg2)
{
if(itemDetails!=null)
{
itemAvailableDict[itemDetails.itemName] = false;
}
}
}
OnBeforeSceneUnloadEvent()
这段代码实现了一个名为 OnBeforeSceneUnloadEvent 的函数,它是在 Unity 场景销毁前执行的事件回调函数。
具体来说,该函数会获取场景中所有类型为 Item 的游戏对象,依次遍历这些游戏对象,然后将每个对象的 itemName 属性作为键值,
在 itemAvailableDict 字典中添加一个布尔型的值(如果该键值不存在)。itemAvailableDict 是一个存储 Item 对象可用性的字典
在这个场景被卸载前,我们需要将场景中所有 Item 对象的可用性信息收集起来,并保存到该字典中。
具体而言,FindObjectsOfType() 是一个函数调用,它返回当前场景中所有类型为 Item 的对象。接着,foreach 循环遍历这些对象,
对于每个对象,程序判断它的 itemName 是否已经存在于 itemAvailableDict 中,如果不存在,则将其添加到 itemAvailableDict 中,对应的布尔值为 true。
这段代码可能运行在 MonoBehavoir 的派生类中,例如 SceneManager 或某个具体的场景控制器,以实现在场景加载和卸载时进行特定的逻辑处理。
小知识
在 Unity 中,private void OnEnable() 是一个生命周期函数,它会在脚本对象被启用时自动调用。
当我们需要对脚本执行初始化操作时,可以在该函数中进行相应的操作。
具体来说,当脚本对象被创建时,默认处于禁用状态,此时 OnEnable() 函数不会被调用。
而当我们手动启用脚本对象(例如将其挂载到场景中的游戏物体上,并勾选其对应的“启用”选项),则 OnEnable() 函数会被调用。
在 OnEnable() 函数中,我们可以进行初始化、注册事件监听器、获取组件等操作,以确保脚本在启用后能够正常运行。
同时,在 OnDisable() 函数中,我们可以进行反初始化、取消事件监听器等操作,以确保脚本在禁用或销毁时不会继续运行或产生错误。
总之,OnEnable() 函数是在脚本对象被启用时自动调用的函数,在其中执行必要的初始化操作可以保证脚本能够正常运行。
1.2
EventHandler
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class EventHandler : MonoBehaviour
{
public static event Action<ItemDetails, int> updateUIEvent;
public static void CallUpdateUIEvent(ItemDetails itemDetails,int index)
{
updateUIEvent?.Invoke(itemDetails, index);
}
/*
创建两个事件来传递或者执行某些函数方法,这两个事件用于提醒订阅了这两个事件的ObjectManager
在场景切换前以及切换到该场景前更新字典的数据
*/
public static event Action BeforeSceneUnloadEvent;
public static void CallBeforeSceneUnloadEvent()
{
BeforeSceneUnloadEvent?.Invoke();
}
public static event Action AfterSceneUnloadEvent;
public static void CallAfterSceneUnloadEvent()
{
AfterSceneUnloadEvent?.Invoke();
}
}
1.3
TransitionManger
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.SceneManagement;//用于场景的加载和卸载
public class TranstionManager : Singleton<TranstionManager>
//TransitionManager用于实现场景的切换,每次点击场景切换箭头后将Teleport to H1 的两个变量传入到TransitonManager
//将TransitionManager设置为单例模式
{
[SceneName] public string startScene;
private bool isFade;//判断是否切换场景
public CanvasGroup fadeCanvasGroup;
public float fadeDuration;//用于控制渐进渐出的速度
private bool canTransition;
//加载游戏一开始的场景
public void Start()
{
StartCoroutine(TransitionToScene (string.Empty, startScene));
}
public void Transition(string from,string to)//用于场景切换
{
if(!isFade&&canTransition)
//运用协程的方法卸载当前的场景,重新加载要去到的那个场景,并且将它设置为Additive
StartCoroutine((IEnumerator)TransitionToScene(from,to));
}
private IEnumerator TransitionToScene(string from,string to)
{
yield return Fade(1);
if(from!=string.Empty)
{
//在场景卸载前,呼叫订阅了该事件的代码执行方法
EventHandler.CallBeforeSceneUnloadEvent();
yield return SceneManager.UnloadScene(from);//卸载当前激活的场景from,并通过yield return语句返回该操作的结果
}
yield return SceneManager.LoadSceneAsync(to, LoadSceneMode.Additive);//使用SceneManager.LoadSceneAsync方法异步加载新场景to,
//并将其添加到场景列表中
//设置新场景为激活场景
Scene newScene = SceneManager.GetSceneAt(SceneManager.sceneCount - 1);
SceneManager.SetActiveScene(newScene);
EventHandler.CallAfterSceneUnloadEvent();
yield return Fade(0);
}
private IEnumerator Fade(float targetAlpha)
{
//传进一个数,如果是0就变成透明的,如果是1就变成黑色
isFade = true;
//核心方法:在场景中创建一个canvas
fadeCanvasGroup.blocksRaycasts = true;
float speed = Math.Abs(fadeCanvasGroup.alpha - targetAlpha)/fadeDuration;
while (!Mathf.Approximately(fadeCanvasGroup.alpha, targetAlpha))
{
fadeCanvasGroup.alpha = Mathf.MoveTowards(fadeCanvasGroup.alpha, targetAlpha,speed * Time.deltaTime);
yield return null;
}
fadeCanvasGroup.blocksRaycasts = false;
isFade = false;
}
}
2 总结
这一节比较简单。