unity3d中的开发,最大的特点就是预制体,不是直接的图片,模型等资源.在一个工程中当然有利于开发,但有个需求这样是不太好的,那就是主逻辑不变,动态加载不同的资源出来.
设计的思路也没什么要说的,非常简单,就是利用反射将你所写的Monobehaiver和ScriptObject类及内部用到的可序列化的字段与相关的类全部转换为只有可序列化的字段,删除方法及属性,删除接口等用不到的信息.然后导入到另一个工程中.此时你会发现预制体可以直接从你原有的工程中导入到这里使用(信息一样的可编辑).最后生成的资源包在原有的工程中也是一样的加载,没有什么不兼容的...
一.基于unity序列化的原理
1.unity序列化一切资源,但主要的还是Monobehaiver和scriptObject.
2.只要让脚本的.meta文件中的guid相同,那么unity会自动关联到使用都身上
3.editor脚本也是scriptObject,所以需要忽略掉
二.代码反射功能提炼出相关信息
1.反射可以将用到的类的索引保存为string信息
2.反射也可以找到对应的公有字段和私有但可以序列化的字段
2.利用这些信息可再结合代码生成的功能,就可以创建出新的类(记得把.meta文件也拷贝过去)
三.基于代码动态生成的技术
/// <summary>
/// 生成新的脚本
/// </summary>
/// <param name="type"></param>
/// <param name="arguments"></param>
/// <returns></returns>
internal static string GenerateNewScirpt(Type type, List<AttributeInfo> attributes, List<Argument> arguments, List<RefineItem> refineList)
{
//声明代码的部分
CodeCompileUnit compunit = new CodeCompileUnit();
CodeNamespace sample = new CodeNamespace(type.Namespace);
compunit.Namespaces.Add(sample);
//引用命名空间
sample.Imports.Add(new CodeNamespaceImport("System"));
//sample.Imports.Add(new CodeNamespaceImport("UnityEngine"));
if (type.IsClass)
{
var cls = GenerateClass(type, attributes, arguments, refineList);
sample.Types.Add(cls);//把这个类添加到命名空间 ,待会儿才会编译这个类
}
else if (type.IsEnum)
{
sample.Types.Add(GenerateEnum(type, arguments));
}
CSharpCodeProvider cprovider = new CSharpCodeProvider();
StringBuilder fileContent = new StringBuilder();
using (StringWriter sw = new StringWriter(fileContent))
{
cprovider.GenerateCodeFromCompileUnit(compunit, sw, new CodeGeneratorOptions());//想把生成的代码保存为cs文件
}
return fileContent.ToString();
}
/// <summary>
/// 生成一个类
/// </summary>
/// <param name="type"></param>
/// <param name="attributes"></param>
/// <param name="arguments"></param>
/// <returns></returns>
private static CodeTypeDeclaration GenerateClass(Type type, List<AttributeInfo> attributes, List<Argument> arguments, List<RefineItem> refineList)
{
var className = type.Name.Contains("`") ? type.Name.Remove(type.Name.IndexOf('`')) : type.Name;
//在命名空间下添加一个类
CodeTypeDeclaration wrapProxyClass = new CodeTypeDeclaration(className);
if (type.IsGenericType)
{
var start = 116;
var count = int.Parse(type.Name.Substring(type.Name.IndexOf('`') + 1, 1));
for (int i = 0; i < count; i++)
{
string p = ((char)(start++)).ToString();
wrapProxyClass.TypeParameters.Add(new CodeTypeParameter(p));
}
}
if (type.BaseType != null)
{
wrapProxyClass.BaseTypes.Add(new CodeTypeReference(type.BaseType));// 如果需要的话 在这里声明继承关系 (基类 , 接口)
}
if (attributes != null)
{
wrapProxyClass.CustomAttributes = GenerateAttributeCollection(attributes);//添加一个Attribute到class上
}
foreach (var item in arguments)
{
System.CodeDom.CodeMemberField field = new CodeMemberField();
field.Type = new CodeTypeReference(item.type);
field.Name = item.name;
field.Attributes = MemberAttributes.Public;
if (!string.IsNullOrEmpty(item.defultValue) && item.type != null && Type.GetType(item.type) != null)
{
var value = Convert.ChangeType(item.defultValue, Type.GetType(item.type));
if (Type.GetType(item.type) == typeof(float) && value.ToString() == "Infinity")
{
Debug.Log("Infinity to " + float.MaxValue);
value = float.MaxValue;
}
field.InitExpression = new CodePrimitiveExpression(value);
}
wrapProxyClass.Members.Add(field);
}
var innserItems = refineList.FindAll(x => x.type == type.FullName + "+" + x.name);
if (innserItems != null)
{
foreach (var item in innserItems)
{
var innerType = Assembly.Load(item.assemble).GetType(item.type);
CodeTypeDeclaration innerClass = null;
if (innerType.IsClass)
{
innerClass = GenerateClass(innerType, item.attributes, item.arguments, refineList);
}
else if (innerType.IsEnum)
{
innerClass = GenerateEnum(innerType, item.arguments);
}
if (innerClass != null)
{
wrapProxyClass.Members.Add(innerClass);
}
}
}
return wrapProxyClass;
}
/// <summary>
/// 生成属性集
/// </summary>
/// <param name="attributes"></param>
/// <returns></returns>
private static CodeAttributeDeclarationCollection GenerateAttributeCollection(List<AttributeInfo> attributes)
{
var collection = new CodeAttributeDeclarationCollection();
foreach (var item in attributes)
{
var att = new CodeAttributeDeclaration(item.attribute);
switch (item.attType)
{
case AttributeInfo.SupportAttributes.RequireComponent:
for (int i = 0; i < item.values.Length; i++)
{
if (!string.IsNullOrEmpty(item.values[i]))
{
var arg = new CodeAttributeArgument(new CodeTypeOfExpression(item.values[i]));
att.Arguments.Add(arg);
}
}
break;
case AttributeInfo.SupportAttributes.CreateAssetMenu:
for (int i = 0; i < item.keys.Length; i++)
{
if (!string.IsNullOrEmpty(item.values[i]))
{
var arg = new CodeAttributeArgument();
arg.Name = item.keys[i];
arg.Value = new CodePrimitiveExpression(item.values[i]);
att.Arguments.Add(arg);
}
}
break;
default:
break;
}
collection.Add(att);
}
return collection;
}
/// <summary>
/// 生成枚举集
/// </summary>
/// <param name="type"></param>
/// <param name="arguments"></param>
/// <returns></returns>
private static CodeTypeDeclaration GenerateEnum(Type type, List<Argument> arguments)
{
CodeTypeDeclaration warpEnum = new CodeTypeDeclaration(type.Name);
warpEnum.IsEnum = true;
foreach (var item in arguments)
{
System.CodeDom.CodeMemberField field = new CodeMemberField();
field.Type = new CodeTypeReference(item.type);
field.Name = item.name;
if (!string.IsNullOrEmpty(item.defultValue))
{
var value = 0;
int.TryParse(item.defultValue, out value);
field.InitExpression = new CodePrimitiveExpression(value);
}
warpEnum.Members.Add(field);
}
return warpEnum;
}
四.来看看最后的效果吧
1.直观的信息编辑面板(支持从文件夹,预制体,脚本,ScriptObject快速导出信息)
2.再看看转换前的一个类和转换后的一个类的样子
转换前:
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using System;
using System.Collections;
using System.Collections.Generic;
namespace WorldActionSystem
{
public class ActionSystem : MonoBehaviour
{
private static ActionSystem instance = default(ActionSystem);
protected ActionSystem() { }
public static ActionSystem Instance
{
get
{
return instance;
}
}
public event UserError onUserError;//步骤操作错误
public event CommandExecute onCommandExecute;
public IRemoteController RemoteController { get { return remoteController; } }
public IActionStap[] ActiveStaps { get { return steps; } }
private IRemoteController remoteController;
private IActionStap[] steps;
private CommandController commandCtrl = new WorldActionSystem.CommandController();
private List<IActionCommand> activeCommands;
private RegistCmds onCommandRegist;
public List<ActionPrefabItem> prefabList = new List<ActionPrefabItem>();
#region Interface Fuctions
private void Awake()
{
instance = this;
}
private void Start()
{
var cmds = new List<ActionCommand>();
RetriveCommand(cmds);//自身加载
CreateAndRegistCommands(cmds);//动态加载
activeCommands = commandCtrl.RegistTriggers(cmds.ToArray(), OnStepComplete, OnCommandExectute);
if (onCommandRegist != null) onCommandRegist.Invoke(activeCommands);
}
private void CreateAndRegistCommands(List<ActionCommand> cmds)
{
CreateObjects((cmd) =>
{
cmd.RegistAsOperate(OnUserError);
cmds.Add(cmd);
});
}
private void RetriveCommand(List<ActionCommand> cmds)
{
RetriveCommand(transform, (cmd) =>
{
cmd.RegistAsOperate(OnUserError);
cmds.Add(cmd);
});
}
#endregion
#region Public Functions
/// <summary>
/// 设置安装顺序并生成最终步骤
/// </summary>
public static IEnumerator LunchActionSystem<T>(T[] steps, UnityAction<ActionSystem, T[]> onLunchOK) where T : IActionStap
{
Debug.Assert(steps != null);
yield return new WaitUntil(() => Instance != null);
Instance.onCommandRegist = (commandList) =>
{
Instance.steps = ConfigSteps<T>(Instance.activeCommands, steps);//重新计算步骤
Instance.activeCommands = GetIActionCommandList(Instance.activeCommands, Instance.steps);
Instance.remoteController = new RemoteController(Instance.activeCommands);
onLunchOK.Invoke(Instance, Array.ConvertAll<IActionStap, T>(Instance.steps, x => (T)x));
};
if (Instance.activeCommands != null)
{
Instance.onCommandRegist.Invoke(Instance.activeCommands);
}
}
#endregion
#region private Funtions
/// <summary>
/// 结束命令
/// </summary>
private void OnStepComplete(string stepName)
{
if(remoteController.CurrCommand != null && remoteController.CurrCommand.StepName == stepName)
{
remoteController.OnEndExecuteCommand();
}
else
{
Debug.LogError("Not Step :" + stepName);
}
}
private void OnCommandExectute(string stepName,int totalCount,int currentID)
{
if(onCommandExecute != null)
{
onCommandExecute.Invoke(stepName, totalCount, currentID);
}
}
/// <summary>
/// 错误触发
/// </summary>
/// <param name="stepName"></param>
/// <param name="error"></param>
private void OnUserError(string stepName, string error)
{
if (onUserError != null) onUserError.Invoke(stepName, error);
}
/// <summary>
/// 当完成命令对象注册
/// </summary>
/// <param name="cmdList"></param>
private void OnCommandRegistComplete(List<IActionCommand> cmdList)
{
instance.activeCommands = cmdList;
if (onCommandRegist != null) onCommandRegist.Invoke(cmdList);
}
internal void CreateObjects(UnityAction<ActionCommand> onCreateCommand)
{
foreach (var item in prefabList)
{
if (item.ignore) continue;
item.prefab.gameObject.SetActive(true);
var created = GameObject.Instantiate(item.prefab);
created.name = item.prefab.name;
if (item.reparent && item.parent != null)
{
created.transform.SetParent(item.parent, false);
}
else
{
created.transform.SetParent(transform, false);
}
if (item.rematrix)
{
TransUtil.LoadmatrixInfo(item.matrix, created.transform);
}
if (item.containsCommand)
{
RetriveCommand(created.transform, onCreateCommand);
}
}
}
private void RetriveCommand(Transform trans, UnityAction<ActionCommand> onRetive)
{
if (!trans.gameObject.activeSelf) return;
var coms = trans.GetComponents<ActionCommand>();
if (coms != null && coms.Length > 0)
{
foreach (var com in coms)
{
onRetive(com);
}
return;
}
else
{
foreach (Transform child in trans)
{
RetriveCommand(child, onRetive);
}
}
}
private void RetivePickElement(Transform trans, UnityAction<PickUpAbleElement> onRetive)
{
if (!trans.gameObject.activeSelf) return;
var com = trans.GetComponent<PickUpAbleElement>();
if (com)
{
onRetive(com);
return;
}
else
{
foreach (Transform child in trans)
{
RetivePickElement(child, onRetive);
}
}
}
/// 重置步骤
/// </summary>
/// <param name="commandDic"></param>
/// <param name="steps"></param>
/// <returns></returns>
private static IActionStap[] ConfigSteps<T>(List<IActionCommand> commandList, T[] steps) where T : IActionStap
{
List<IActionStap> activeStaps = new List<IActionStap>();
List<string> ignored = new List<string>();
for (int i = 0; i < steps.Length; i++)
{
var old = commandList.Find(x => x.StepName == steps[i].StapName);
if (old != null)
{
activeStaps.Add(steps[i]);
}
else
{
ignored.Add(steps[i].StapName);
}
}
Debug.Log("[Ignored steps:]" + String.Join("|", ignored.ToArray()));
return activeStaps.ToArray();
}
/// <summary>
/// 得到排序后的命令列表
/// </summary>
/// <returns></returns>
private static List<IActionCommand> GetIActionCommandList(List<IActionCommand> commandList, IActionStap[] steps)
{
var actionCommandList = new List<IActionCommand>();
foreach (var item in steps)
{
var old = commandList.Find(x => x.StepName == item.StapName);
if (old != null)
{
actionCommandList.Add(old);
}
else
{
Debug.LogWarning(item + "已经存在");
}
}
return actionCommandList;
}
#endregion
private void OnDestroy()
{
ElementController.Clean();
EventController.Clean();
Setting.ResetDefult();
}
}
}
转换后:
namespace WorldActionSystem {
using System;
public class ActionSystem : UnityEngine.MonoBehaviour {
public System.Collections.Generic.List<WorldActionSystem.ActionPrefabItem> prefabList;
}
}
最后,还是开源的地址,有兴趣的可以搞来玩玩:https://github.com/zouhunter/ScriptsRefine.git