这两天一直在研究CSLight,目前Unity热更新的方式有两种,一种是ulua这个网上的例子已经很多了。还有一种就是CSLight。其实我更希望CSLight可以趋向成熟,因为它的语法就是C#,但是有些C#的标准语法用不了。这两天我学习的做了一个例子,也把我遇到的坑记录一下。

1.在github上下载CSLight,当我把DLL拖进项目的时候会报错。原因是CSLight的dll和NGUI的冲突了,所以我直接把他的core文件夹代码全部拷贝在我的工程里面。
2.脚本可以直接就创建成.cs文件,这样可以利用unity的语法提示。李总真是太聪明了哈哈。
3.脚本与类之间传递参数。。如下脚本所示,调用脚本UIMain中的Start()方法,并且将名子作为参数传递了进去。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
public class UICommon : MonoBehaviour {
void Start ()
{
ScriptMgr.Instance.LoadProject();
ScriptMgr.Instance.Execute("UIMain.Start(""+name+"");");
}
}
ScriptMagr是李总封装的脚本管理类,是一个静态类。因为我们在脚本中可能会用到一些数据对象,需要提前注册一下,每次打开界面都去注册一下显然不太好。LoadProject()等于就是开游戏的时候注册一下,以后直接就去用。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System;
/// <summary>
/// 这个类实现脚本的Logger接口,脚本编译时的信息会从Log输出出来
/// </summary>
class ScriptLogger : CSLE.ICLS_Logger
{
public void Log(string str)
{
UnityEngine.Debug.Log(str);
}
public void Log_Error(string str)
{
Debug.LogError(str);
}
public void Log_Warn(string str)
{
Debug.LogWarning(str);
}
}
public class ScriptMgr
{
/// <summary>
/// ScriptMgr用单例模式,主要是为了提供C#Light Env的初始化
/// </summary>
public static ScriptMgr Instance
{
get
{
if (g_this == null)
g_this = new ScriptMgr();
return g_this;
}
}
#region forInstance
static ScriptMgr g_this;
public CSLE.CLS_Environment env
{
get;
private set;
}
private ScriptMgr()
{
env = new CSLE.CLS_Environment(new ScriptLogger());
env.logger.Log("C#LightEvil Inited.Ver=" + env.version);
RegTypes();
}
#endregion
/// <summary>
/// 这里注册脚本有权访问的类型,大部分类型用RegHelper_Type提供即可
/// </summary>
void RegTypes()
{
//大部分类型用RegHelper_Type提供即可
env.RegType(new CSLE.RegHelper_Type(typeof(Vector2)));
env.RegType(new CSLE.RegHelper_Type(typeof(Vector3)));
env.RegType(new CSLE.RegHelper_Type(typeof(Vector4)));
env.RegType(new CSLE.RegHelper_Type(typeof(Time)));
env.RegType(new CSLE.RegHelper_Type(typeof(Debug)));
env.RegType(new CSLE.RegHelper_Type(typeof(GameObject)));
env.RegType(new CSLE.RegHelper_Type(typeof(Component)));
env.RegType(new CSLE.RegHelper_Type(typeof(UnityEngine.Object)));
env.RegType(new CSLE.RegHelper_Type(typeof(Transform)));
env.RegType(new CSLE.RegHelper_Type(typeof(Resources)));
//对于AOT环境,比如IOS,get set不能用RegHelper直接提供,就用AOTExt里面提供的对应类替换
env.RegType(new CSLE.RegHelper_Type(typeof(int[]), "int[]"));//数组要独立注册
env.RegType(new CSLE.RegHelper_Type(typeof(List<int>), "List<int>"));//模板类要独立注册
//每一种回调类型要独立注册
env.RegDeleType(new CSLE.RegHelper_DeleAction("Action")); //unity 用的dotnet 2.0 没有Action
env.RegDeleType(new CSLE.RegHelper_DeleAction<int>("Action<int>")); ;
env.RegDeleType(new CSLE.RegHelper_DeleAction<GameObject>("Action<GameObject>")); ;
env.RegType(new CSLE.RegHelper_Type(typeof(Rect)));
env.RegType(new CSLE.RegHelper_Type(typeof(PrimitiveType)));
env.RegType(new CSLE.RegHelper_Type(typeof(UICommonEvent)));
env.RegType(new CSLE.RegHelper_Type(typeof(UISprite)));
}
public bool projectLoaded
{
get;
private set;
}
public void LoadProject()
{
if (projectLoaded) return;
try
{
string[] files = System.IO.Directory.GetFiles(Application.streamingAssetsPath, "*.cs", System.IO.SearchOption.AllDirectories);
Dictionary<string, IList<CSLE.Token>> project = new Dictionary<string, IList<CSLE.Token>>();
foreach (var v in files)
{
var tokens = env.tokenParser.Parse(System.IO.File.ReadAllText(v));
project.Add(v, tokens);
}
env.Project_Compiler(project, true);
projectLoaded = true;
}
catch (Exception err)
{
Debug.LogError("编译脚本项目失败,请检查" + err.ToString());
}
}
public void Execute(string code)
{
var content = env.CreateContent();
try
{
var tokens = env.ParserToken(code);
var expr = env.Expr_CompilerToken(tokens);
expr.ComputeValue(content);
}
catch (Exception err)
{
var dumpv = content.DumpValue();
var dumps = content.DumpStack(null);
var dumpSys = err.ToString();
Debug.LogError(dumpv + dumps + dumpSys);
}
}
}
大家注意看RegTypes()里面的注册方法。把你的脚本中用到的类,可以是unity提供的类,可以是NGUI提供的类,也可以是你自己封装的类都在这里注册一下,只有注册了你的脚本里才能使用这些方法。
假如界面Prefab在Assetbundle里面热更新了,那么脚本也对应需要更新,比如之前的界面只有一个按钮,那么新更新一个界面有两个按钮了,那么需要给新增加的按钮加监听事件。先看看下面可以热更新的这条脚本。
using UnityEngine;
using System.Collections;
public class UIMain {
static GameObject button ;
//UICommon里面调用脚本的Start方法,并且传入了名子的字符串
static void Start (string root)
{
Transform rootUI = GameObject.Find(root).transform;
//在Resources下面读取资源或者 Assetbundle来读取,并且实例化在场景视图中。这一句操作我觉得也可以在Unity的脚本中完成
GameObject prefab = Resources.Load("UIMain") as GameObject;
GameObject gameObject = Object.Instantiate(prefab) as GameObject;
//放在UIPanel下面,让坐标在原点
gameObject.transform.parent = rootUI;
gameObject.transform.localPosition = Vector3.zero;
gameObject.transform.localScale = Vector3.one;
Transform transform = gameObject.transform;
//找到UIPrefab下面的一个Sprite并且修改一下Sprite的名子。
UISprite sprite = transform.Find("Sprite").GetComponent("UISprite")as UISprite;
sprite.spriteName ="Glow";
sprite.transform.localPosition = new Vector3 (10,100,0);
sprite.MakePixelPerfect();
//获取按钮对象,并且增加按钮的监听
button = transform.Find("Button").gameObject;
UICommonEvent.onClick +=Click;
UICommonEvent.AddOnClick(button);
}
//当按钮点击的时候在脚本中得到回调
static void Click(GameObject go)
{
if(go.name == button.name){
Debug.Log("雨松MOMO提示,您点击了这个按钮喔");
}
}
}
代码写完你会发现几乎和C#的脚本万全一样,但是有几个比较恶心的地方。
1.不支持范型。
热更新的这样的代码就不能直接使用了。
Resources.Load<GameObject>
gameObject.GetComponent<UISprite>
2.不支持typeof()关键字
3.不支持代理事件。
用NGUI做界面,可能里面用了大量的UIEventListener ,比如按钮、精灵的位移动画等等。这种东西如果硬要在脚本里面写太蛋疼了。我觉得最好还是把代理相关的东西拿出来。
还有就是做界面的时候可能会用到一些定时器,如果用代理来做的话也需要改改,总之向这种delegate回调的地方应该都要封装成方法,然后在回调进脚本里面。
在上面的代码中,我在处理按钮的点击事件的时候。如下代码所示,我写了一条Unity的脚本,在热更新的脚本里面,通过类名.就可以直接访问方法并且传递参数。UIEventListener监听到事件以后,在回调一下热更新脚本中的方法。
class UICommonEvent{
public static event Action<GameObject> onClick;
static public void AddOnClick(GameObject button)
{
UIEventListener.Get(button).onClick =delegate(GameObject go) {
onClick(go);
};
}
}
注:我也不是CSLight的高手,也是最近开始学。感谢作者给我了很大帮助,他的游戏项目中大量的使用CSLight。据说效率还可以,这两天我在好好测试一下它的效率,希望有经验的朋友可以分享一些。谢谢啦。
最后本文下载:http://pan.baidu.com/s/1c0Ehn0G
- 本文固定链接: https://www.xuanyusong.com/archives/3088
- 转载请注明: 雨松MOMO于 雨松MOMO程序研究院 发表

请问下ScriptMgr.Instance.Execute(“”)这里面可以传Collider吗??就是不是字符串类型的参数
在哪里下载?
ScriptMgr.Instance.Execute(“UIMain.Start(“” name “”);”);可以执行服务器的脚本吗比如:ScriptMgr.Instance.Execute(“https://www.xuanyusong.com/UIMain.Start(“” name “”);”);
ScriptMgr.Instance.Execute(“UIMain.Start(“”+name+””);”);可以执行服务器的脚本吗比如:ScriptMgr.Instance.Execute(“https://www.xuanyusong.com/UIMain.Start(“”+name+””);”);
可以具体做些什么事呢?好像demo中只是在对象上添加了NGUI的脚本。。可不可以添加服务器上自己写的脚本组件到游戏对象上呢?
这个只是读取CS文件。。但是打好apk包之后读取cs脚本会不会代码泄露什么的呢。。。
momo可以出一个游戏热更新,检查版本和检查要更新的资源的博客吗,网上很多都是从页游那边出来的方法,感觉很复杂,相信大家都期待有这样一篇文章作为引导!
最近要做热更新,打算用lua,momo一介绍CSLight,又纠结了。但是担心CSLight太新了,坑太多,不敢用。momo最好在弄个详细点的demo,介绍如何热更新复杂窗体,甚至逻辑更新。一定会造福广大u友的。毕竟c#敲着才顺手。