在Unity中资源的加载有很多方式,通过Resource去进行动态加载,当需要热更新的时候,就需要通过加载AB包的方式,如果单独用一种方式进行加载的话,当需要用到另外一种的时候,往往需要对逻辑进行较大的更改,更改代码永远是不好的,这个时候我们面向接口,到时候只需要更换加载,而不需要去管代码的逻辑。资源的动态加载以及脚本的动态挂载,有两种方式,第一种需要预制体的名字和脚本名字保持一致,通过Type.GetType的方式获取到当前脚本的类型,那这样看到第一种相对简单也算是也一点限制,第二种呢,通过Attribute特性的方式去动态挂载脚本。
一.第一种动态资源加载和脚本挂载
1.第一种动态资源加载和脚本挂载,跟根据需要加载的参数,写出加载的方法,创建一个名为ResourceLoad的方法以及ABLoad的方法,全部继承至ILoad的接口实现动态加载资源。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ResourceLoad :ILoad
{
public GameObject LoadPrefab(string path, Transform parent = null)
{
GameObject temp = Resources.Load<GameObject>(path);
GameObject go = UnityEngine.Object.Instantiate(temp, parent);
return go;
}
}
using UnityEngine;
public class ABLoad : ILoad
{
public GameObject LoadPrefab(string path, Transform parent = null)
{
return null;
}
}
using UnityEngine;
public interface ILoad
{
GameObject LoadPrefab(string path, Transform parent = null);
}
2.接下来我们需要创建一个加载的管理类,在这个管理器里面可以选择加载方式,资源的加载和脚本的添加都在这个管理器进行实现。接下来先实现第一种方式,通过得到接口,但是却可以选择new不同的子类,这里面就运用到了里氏转换原则,可以将子类向上转换为父类进行实现。通过LoadPrefab加载资源,通过Type.GetType得到当前预制体类型,然后动态添加,这种方式必须要求预制体名称和脚本名称一致。
using UnityEngine;
using System;
public class LoadManager : NormalSingeleton<LoadManager>
{
private ILoad loader;
public LoadManager()
{
loader = new ResourceLoad(); //Resource加载
//loader = new ABLoad(); //AB加载
}
/// <summary>
/// 1.预制体名称和脚本名称保持一致,使用Type.GetType获得预制体的脚本类型
/// </summary>
public GameObject LoadPrefab(string path, Transform parent = null)
{
GameObject temp = loader.LoadPrefab(path, parent);
Type type = Type.GetType(temp.name.Remove(temp.name.Length - 7));
temp.AddComponent(type);
return temp;
}
}
3.接下创建一个LaunchGame脚本作为场景的启动脚本,用于调用LoadManager的加载预制体的方法,传入加载的路径和父物体。
using UnityEngine;
public class LaunchGame : MonoBehaviour
{
void Start()
{
LoadManager.Instance.LoadPrefab("Prefabs/StartView", transform);
}
void Update()
{
}
}
4.最后运行的效果如下
二.第二种运用特性动态资源加载和脚本挂载
1.下面我们运用第二种特性的方法进行动态加载资源以及动态添加脚本,创建一个名为BindPrefab的脚本,该脚本继承至Attribute,自定义一个特性,只应用于Class,在构造方法初始化资源的路径
using System;
[AttributeUsage(AttributeTargets.Class)]
public class BindPrefab :Attribute
{
public string path;
public BindPrefab(string path)
{
this.path = path;
}
}
2.创建一个名为BindUtil的工具类,创建一个字典用于key用于存储路径,value存储类型,有Bind和Load两个方法,Bind方法添加路径和类型,Load方法跟据路径得到类型
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class BindUtil
{
private static Dictionary<string, Type> prefabAndScritMaps = new Dictionary<string, Type>();
/// <summary>
/// 绑定预制体
/// </summary>
public static void Bind(string path,Type type)
{
//Debug.Log("Path:" + path);
if (!prefabAndScritMaps.ContainsKey(path))
{
prefabAndScritMaps.Add(path, type);
}
else
{
Debug.LogError("当前数据这已经存在该路径" + path);
}
}
/// <summary>
/// 加载预制体
/// </summary>
/// <returns></returns>
public static Type Load(string path)
{
//Debug.Log("Path:" + path);
if (prefabAndScritMaps.ContainsKey(path))
{
return prefabAndScritMaps[path];
}
else
{
Debug.LogError("当前数据中不存在该路径");
return null;
}
}
}
3.创建一个名为IntiCustomAttribute的脚本,对BindPrefab我们自定义的特性进行初始化,先是得到BindPrefab类中的程序集,之后得到该程序集下所有共有的方法,检索每个Type类型所拥有的特性,然后强制转换为当前类型,调用BindUtil的方法将路径和类型进行绑定。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using UnityEngine;
/// <summary>
/// 初始化特性
/// </summary>
public class InitCustomAttribute
{
public void Init()
{
//获取在其中定义指定类型的当前加载的程序集。返回的程序集中的类型。
Assembly assembly = Assembly.GetAssembly(typeof(BindPrefab));
//Debug.Log("Assembly:" + assembly.FullName);
//Debug.Log("Assembly:" + assembly.CodeBase);
//得到该程序集种所有的Type类型,也就是得到该程序集下面所有共有的方法
Type[] types = assembly.GetExportedTypes();
foreach (Type type in types)
{
//Debug.Log("Type:" + type);
//得到每个Type类型所拥有的特性
foreach (Attribute attribute in Attribute.GetCustomAttributes(type, true))
{
Debug.Log("Attribute:"+attribute);
if(attribute is BindPrefab)
{
BindPrefab date = attribute as BindPrefab;
BindUtil.Bind(date.path, type);
}
}
}
}
}
4.在StartView脚本中,使用该特性,传入资源的路径,就相当于调用BindPrefab的构造函数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[BindPrefab("Prefabs/StartView")]
public class StartView : MonoBehaviour
{
void Start()
{
}
void Update()
{
}
}
5.在LoadManager得到资源和动态挂载脚本,通过BindUtil.Load得到类型,进行动态添加
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
public class LoadManager : NormalSingeleton<LoadManager>
{
private ILoad loader;
public LoadManager()
{
loader = new ResourceLoad(); //Resource加载
//loader = new ABLoad(); //AB加载
}
/// <summary>
/// 1.预制体名称和脚本名称保持一致,使用Type.GetType获得预制体的脚本类型
/// </summary>
//public GameObject LoadPrefab(string path, Transform parent = null)
//{
// GameObject temp = loader.LoadPrefab(path, parent);
// Type type = Type.GetType(temp.name.Remove(temp.name.Length - 7));
// temp.AddComponent(type);
// return temp;
//}
public GameObject LoadPrefab(string path, Transform parent = null)
{
GameObject temp = loader.LoadPrefab(path, parent);
Type type = BindUtil.Load(path);
temp.AddComponent(type);
return temp;
}
}
6.最后在我们的LaunchGame中进行初始化和加载
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class LaunchGame : MonoBehaviour
{
void Start()
{
InitCustomAttribute initCustomAttr = new InitCustomAttribute();
initCustomAttr.Init();
LoadManager.Instance.LoadPrefab("Prefabs/StartView", transform);
}
}
7.得到一样的效果