- A+
说到想法,那就先说说想法了。这段时间就在想,人生有了一个笔记本和一个博客可以记录你的东西,这些确实一些不是很具有生动的展现,所以我就想弄一个游戏展示的app,可以把自己的一些想法和实现所呈现的效果展示出来,这样拿着手机的我就可以看到这些东西了。
为了避免频繁的打包,例如每次一个想法就打包安装一次,这样体验就太差了。所以我就想着用ToLua来做代码的而更新,为了代码尽量都能更新,所以运行的逻辑将大部分都放在lua上面。
大概的思路的是这样的,首先在一个入口场景中,初始化信息并将触发的调用方法转移到lua那边去执行,以后的逻辑就放在那边,c#这边将会做一个辅助功能,方便lua那边去使用。然后在到更新的场景,这里要做的就是把资源和代码更新到最新,资源的话我会选择打包整个场景,方便又快捷,代码的话没什么好说的,轻量级别的直接单文件下载得了。再然后到一个大厅得地方,获取关卡列表,展示UI。最后就是点击关卡,就加载相应得关卡场景了。
先来看看启动场景,用于框架的启动和场景加载进度条的实现,其他内容功能性场景都会一叠加的方式添加出来,启动场景将永远保留在内存中。
启动场景挂载了一个启动脚本,用来初始化Lua虚拟机及转移一些Unity引擎驱动的方法,如Awake、Start、Update方法等。还用来提供一些功能性的方法,如保存文件等等
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 |
using UnityEngine; using LuaInterface; using System; using System.IO; public class Main : MonoBehaviour { public LuaState luaState = null; public static Main Instance = null; private LuaFunction onEventCall = null; private LuaFunction onUpdate = null; private LuaLooper loop = null; private string tips = ""; public bool useRemoteRes = true; private void Awake() { Main.Instance = this; Screen.sleepTimeout = SleepTimeout.NeverSleep; InitLuaEvn(); } void Start () { luaState.DoFile("Main"); this.onUpdate = luaState["Update"] as LuaFunction; luaState.Invoke<GameObject, bool>("Init",this.gameObject,true); } void OnApplicationQuit() { onEventCall.Dispose(); onUpdate.Dispose(); luaState.Dispose(); luaState = null; if (loop != null) { loop.Destroy(); loop = null; } } void Update () { //luaState.Invoke<float, bool>("Update", Time.deltaTime, true); this.onUpdate.BeginPCall(); this.onUpdate.Push(Time.deltaTime); this.onUpdate.PCall(); this.onUpdate.EndPCall(); } private void InitLuaEvn() { new LuaResLoader(); luaState = new LuaState(); if (this.useRemoteRes) { luaState.AddSearchPath(Application.persistentDataPath + "/Updates/Src"); } luaState.Start(); luaState["useRemoteRes"] = this.useRemoteRes; luaState.LogGC = false; DelegateFactory.Init(); LuaBinder.Bind(luaState); loop = gameObject.AddComponent<LuaLooper>(); loop.luaState = luaState; OpenLibs(); OpenCJson(); } protected void OpenLibs() { luaState.OpenLibs(LuaDLL.luaopen_pb); luaState.OpenLibs(LuaDLL.luaopen_struct); luaState.OpenLibs(LuaDLL.luaopen_lpeg); #if UNITY_STANDALONE_OSX || UNITY_EDITOR_OSX luaState.OpenLibs(LuaDLL.luaopen_bit); #endif if (LuaConst.openLuaSocket) { OpenLuaSocket(); } } protected void OpenLuaSocket() { LuaConst.openLuaSocket = true; luaState.BeginPreLoad(); luaState.RegFunction("socket.core", LuaOpen_Socket_Core); luaState.RegFunction("mime.core", LuaOpen_Mime_Core); luaState.EndPreLoad(); } protected void OpenCJson() { luaState.LuaGetField(LuaIndexes.LUA_REGISTRYINDEX, "_LOADED"); luaState.OpenLibs(LuaDLL.luaopen_cjson); luaState.LuaSetField(-2, "cjson"); luaState.OpenLibs(LuaDLL.luaopen_cjson_safe); luaState.LuaSetField(-2, "cjson.safe"); } [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] static int LuaOpen_Socket_Core(IntPtr L) { return LuaDLL.luaopen_socket_core(L); } [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))] static int LuaOpen_Mime_Core(IntPtr L) { return LuaDLL.luaopen_mime_core(L); } public void RegistEventCall(LuaFunction func) { this.onEventCall = func; } public void InvokeEventCall(GameObject go,string type,object o) { if (this.onEventCall != null) { this.onEventCall.BeginPCall(); this.onEventCall.Push(go); this.onEventCall.Push(type); this.onEventCall.Push(o); this.onEventCall.PCall(); this.onEventCall.EndPCall(); } } public void SaveDownLoadData(string path,byte[] datas) { string s = path.Substring(0, path.LastIndexOf('/')); DirectoryInfo directoryInfo = new DirectoryInfo(s); if(!directoryInfo.Exists) Directory.CreateDirectory(s); FileInfo file = new FileInfo(@path); Stream sw; sw = file.Create(); //全部重下 sw.Write(datas, 0, datas.Length); sw.Flush(); sw.Close(); sw.Dispose(); } } |
可以看出初始化的时候加载了Main.lua的脚本文件,并把相应的引擎方法Start、Update方法转移到Main.lua的相应方法中,这样调用是没有考虑性能问题的,暂时也没有发现特别耗时,相反的看到log输出更加耗时。下面看看Lua那边的启动文件Main.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
--全局引入 require "Common.class" require "Common.Const" require "Common.Managers" require "Common.functions" function Init(go) GameManager:GetInstance():Init(go) return true end function Update(dt) GameManager:GetInstance():Update(dt) end return Main |
很简单的一个Lua启动脚本,首先是全局的引入一些公用常用的方法和常量还有管理器,提供那两个Unity驱动调用的方法Init和Update,在这两个方法里面调用游戏管理器的相应方法,从而驱动整个游戏的运行,下面就看看这个游戏管理器GameManager.lua
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
--管理游戏逻辑的驱动运行 local ManagerBase = require("Base.ManagerBase") local GameManager = class("Manager.GameManager",ManagerBase) function GameManager:Init(go) self.updates = {} self.delays = {} self.goGameObject = go UtilityManager:GetInstance():Init() PathManager:GetInstance():Init() MySceneManager:GetInstance():Init(go) end function GameManager:Update(dt) MySceneManager:GetInstance():Update(dt) for k,delay in pairs(self.delays) do if delay then delay.time = delay.time - dt if delay.time <= 0 then delay.handler(delay.param) self.delays[k] = nil end end end for k,update in ipairs(self.updates) do update.ticker = update.ticker + dt if update.ticker >= update.timer then update.handler(update.ticker) update.ticker = 0 end end end function GameManager:RegistUpdate(timer,handler) local update = {} update.timer = timer update.handler = handler update.ticker = 0 table.insert(self.updates,update) return handler end function GameManager:RemoveUpdate(handler) local targetIndex = 0 for i = 1,#self.updates do if self.updates[i].handler == handler then targetIndex = i break end end if targetIndex ~= 0 then table.remove(self.updates,targetIndex) end end function GameManager:InvokeDelay(time,handler,...) local delay = {} delay.time = time delay.handler = handler delay.param = ... table.insert(self.delays,delay) end function GameManager:Reset() self.updates = {} end return GameManager |
游戏管理器是一个单例模式管理器,方便调用,这里大部分管理器都会运用单例模式,主要是为了方便。在初始化方法中,初始化相应需要初始化的管理器,这里就先不一一的讲了,后面有用到的时候在说把。除了初始化管理器,你也看到了一个updates的table,保存了用方法RegistUpdate()注册的触发器,通过Unity驱动的Update的方法实现相应的计时,达到要求的就调用handler回调,从而驱动游戏单元的每帧执行逻辑和定时执行逻辑。所有的lua脚本都可以通过GameManager这个单例管理器去注册自己的定时执行方法,从而驱动游戏逻辑的运行。在不需要更新逻辑或则脚本释放的时候,记得移除update不然可能会引起错误。InvokeDelay会延迟调用指定的方法,调用完就丢弃了。
那个handler是一个在functions.lua里面的handler方法生成的方法,为的就是辨别self是谁,下面看看这个方法。
1 2 3 4 5 |
function handler(target,func) return function(...) return func(target,...) end end |
返回一个方法就是我们说的回调,调用的时候指定调用方,防止调用方的不正确从而里面的self获取不到当前类的属性方法。
到场景1的预览效果: