首页 >Unity3D频道 >【CSLight研究院】 > CSLight研究院之学习笔记结合NGUI(一)
2014
08-12

CSLight研究院之学习笔记结合NGUI(一)


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

Foxmail20140812_1

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

最后编辑:
作者:雨松MOMO
专注移动互联网,Unity3D游戏开发
捐 赠写博客不易,如果您想请我喝一杯星巴克的话?就进来看吧!

CSLight研究院之学习笔记结合NGUI(一)》有 10 条评论

  1. 吴继车说:

    请问下ScriptMgr.Instance.Execute(“”)这里面可以传Collider吗??就是不是字符串类型的参数

  2. domo说:

    ScriptMgr.Instance.Execute(“UIMain.Start(“” name “”);”);可以执行服务器的脚本吗比如:ScriptMgr.Instance.Execute(“https://www.xuanyusong.com/UIMain.Start(“” name “”);”);

  3. domo说:

    ScriptMgr.Instance.Execute(“UIMain.Start(“”+name+””);”);可以执行服务器的脚本吗比如:ScriptMgr.Instance.Execute(“https://www.xuanyusong.com/UIMain.Start(“”+name+””);”);

  4. domo说:

    可以具体做些什么事呢?好像demo中只是在对象上添加了NGUI的脚本。。可不可以添加服务器上自己写的脚本组件到游戏对象上呢?

  5. domo说:

    这个只是读取CS文件。。但是打好apk包之后读取cs脚本会不会代码泄露什么的呢。。。

  6. Francis说:

    momo可以出一个游戏热更新,检查版本和检查要更新的资源的博客吗,网上很多都是从页游那边出来的方法,感觉很复杂,相信大家都期待有这样一篇文章作为引导!

  7. AK--47说:

    最近要做热更新,打算用lua,momo一介绍CSLight,又纠结了。但是担心CSLight太新了,坑太多,不敢用。momo最好在弄个详细点的demo,介绍如何热更新复杂窗体,甚至逻辑更新。一定会造福广大u友的。毕竟c#敲着才顺手。

留下一个回复

你的email不会被公开。 必填项已用 * 标注。