|
前言:上个项目终于告一段落了,进入了内测阶段,趁着这段空闲时间,主程让我这边预研一下FairyGui、xAssets、unity的Addressable、xlua。下面主要讲一下对fgui的理解和自动导出lua和与xlua的整合。
<hr/>Fgui简介
FairyGUI是一款UI制作的编辑器,它是一个跨平台的UI编辑器,支持多个开发平台。
http://www.fairygui.com/ 是它的官网。它的跨平台性主要体现在FairyGUI编辑器上。
这一点和UnityEditor非常相似,做布局、动画这些只用做一次,然后选择不同的平台,生成一个UI包, 然后在不同的平台上用不同的SDK去加载UI包、关联UI事件触发等。
Fgui能做到什么?
FairyGUI,解决了 Unity 制作 UI 的很多痛点,例如 - 多国语言支持 - 可视化编辑 UI 动画 - 图文混排(包括文字和动画混排) - 虚拟列表(数量巨大的列表数目也不卡顿) - 循环列表 - 3D UI - VR UI(轻松制作出曲面 UI), - 内置手势库等
FairyGUI还内置了许多常用的组件,不需要客户端这边再重新实现。
FGUI的优点
FariyGUI提供了一个自己的独立编辑器,基本所有UI功能都能实现。FGUI对美术十分友好,各种习惯跟Adobe系列的一致,编辑器本身就是AS3开发的。包装了一些概念,十分方便拼界面工作;如关联=>屏幕适配 、控制器=>状态控制等等。开发效率高,这种开发模式的效率是其他UI远远不能比的,越大的项目优势越明显。UI开发有一个很大的特点是重构次数比较多,FaityGUi只需要程序处理逻辑相关的更新,节省了拼ui的工作。性能问题,NGUI/UGUI为了降低DrawCall采用的合并Mesh技术对UI制作有要求,一旦动静分离不合理会引发灾难。FairyGUI提供的优化技术是动态后期优化,在制作时对UI人员基本没有要求。简单的说,就是你随便拼,FairyGUI负责自动优化。
FGUI的缺点
调试缺点,FairyGUI的组件并不是MonoBehaviour类型的,所以在Unity里面看到的GameObject并不能和组件一一对应,遇到一些奇怪问题的时候需要一定想象力,不能像UGUI或者NGUI简单直接看到UI的参数。FairyGUI需要学习多一个编辑器,了解一种不同的开发模式。
使用流程
因此使用流程如下: - 用FairyGUI Editor建立一个UI工程 - 美术导入必要的素材 - 美术做好界面 - 美术打包和发布代码(c# lua均可) - 自动导出到unity - 程序在脚本里写业务逻辑
fgui与xlua的整合
以下内容参考一条非洲人的博客,感谢分享。 - 首先将官网unity项目的源码和luaSupport拷贝过来
可以看到LuaSupport里有两个文件,一个是tolua的,一个是xlua的,选择其一放到你的lua工程下。 这里也分享下我的FairyGUI.lua
EventContext = CS.FairyGUI.EventContext
EventListener = CS.FairyGUI.EventListener
EventDispatcher = CS.FairyGUI.EventDispatcher
InputEvent = CS.FairyGUI.InputEvent
NTexture = CS.FairyGUI.NTexture
Container = CS.FairyGUI.Container
Image = CS.FairyGUI.Image
Stage = CS.FairyGUI.Stage
Controller = CS.FairyGUI.Controller
GObject = CS.FairyGUI.GObject
GGraph = CS.FairyGUI.GGraph
GGroup = CS.FairyGUI.GGroup
GImage = CS.FairyGUI.GImage
GLoader = CS.FairyGUI.GLoader
GMovieClip = CS.FairyGUI.GMovieClip
TextFormat = CS.FairyGUI.TextFormat
GTextField = CS.FairyGUI.GTextField
GRichTextField = CS.FairyGUI.GRichTextField
GTextInput = CS.FairyGUI.GTextInput
GComponent = CS.FairyGUI.GComponent
GList = CS.FairyGUI.GList
GRoot = CS.FairyGUI.GRoot
GLabel = CS.FairyGUI.GLabel
GButton = CS.FairyGUI.GButton
GComboBox = CS.FairyGUI.GComboBox
GProgressBar = CS.FairyGUI.GProgressBar
GSlider = CS.FairyGUI.GSlider
PopupMenu = CS.FairyGUI.PopupMenu
ScrollPane = CS.FairyGUI.ScrollPane
Transition = CS.FairyGUI.Transition
UIPackage = CS.FairyGUI.UIPackage
Window = CS.FairyGUI.Window
GObjectPool = CS.FairyGUI.GObjectPool
Relations = CS.FairyGUI.Relations
RelationType = CS.FairyGUI.RelationType
UIPanel = CS.FairyGUI.UIPanel
UIPainter = CS.FairyGUI.UIPainter
TypingEffect = CS.FairyGUI.TypingEffect
UIObjectFactory = CS.FairyGUI.UIObjectFactory
xutil = require &#39;xlua.util&#39;
fgui = {}
--[[
用于继承FairyGUI的Window类,例如
WindowBase = fgui.window_class()
同时派生的Window类可以被继续被继承,例如
MyWindow = fgui.window_class(WindowBase)
可以重写的方法有(与Window类里的同名方法含义完全相同)
OnInit
DoHideAnimation
DoShowAnimation
OnShown
OnHide
]]
function fgui.window_class(base)
local o = {}
FairyGUI = CS.FairyGUI
local base = base or FairyGUI.LuaWindow
setmetatable(o, base)
o.__index = o
o.base = base
o.New = function(...)
local t = {}
--setmetatable(t, o)
setmetatable(t, o)
local ins = FairyGUI.LuaWindow()
-- tolua.setpeer(ins, t)
xutil.state(ins, t)
ins:ConnectLua(t)
t.EventDelegates = {}
if t.ctor then
t.ctor(ins,...)
end
return ins
end
return o
end
--[[
注册组件扩展,例如
fgui.register_extension(UIPackage.GetItemURL(&#34;包名&#34;,&#34;组件名&#34;), my_extension)
my_extension的定义方式见fgui.extension_class
]]
function fgui.register_extension(url, extension)
local base = extension.base
if base==GComponent then base=FairyGUI.GLuaComponent
elseif base==GLabel then base=FairyGUI.GLuaLabel
elseif base==GButton then base=FairyGUI.GLuaButton
elseif base==GSlider then base=FairyGUI.GLuaSlider
elseif base==GProgressBar then base=FairyGUI.GLuaProgressBar
elseif base==GComboBox then base=FairyGUI.GLuaComboBox
else
print(&#34;invalid extension base: &#34;..base)
return
end
FairyGUI.LuaUIHelper.SetExtension(url, typeof(base), extension.Extend)
end
--[[
用于继承FairyGUI原来的组件类,例如
MyComponent = fgui.extension_class(GComponent)
function MyComponent:ctor() --当组件构建完成时此方法被调用
print(self:GetChild(&#34;n1&#34;))
end
]]
function fgui.extension_class(base)
local o = {}
o.__index = o
o.base = base or GComponent
o.Extend = function(ins)
local t = {}
setmetatable(t, o)
tolua.setpeer(ins,t)
ins.EventDelegates = {}
if t.ctor then
t.ctor(ins)
end
return t
end
return o
end
--[[
FairyGUI自带的定时器,有需要可以使用。例如:
每秒回调,无限次数
fgui.add_timer(1,0,callback)
可以带self参数
fgui.add_timer(1,0,callback,self)
可以自定义参数
fgui.add_timer(1,0,callback,self,data)
!!警告,定时器回调函数不要与UI事件回调函数共用
]]
function fgui.add_timer(interval, repeatCount, func, obj, param)
local callbackParam
if param~=nil then
if obj==nil then
callbackParam=param
else
callbackParam=obj
func = function(p)
func(p, param)
end
end
end
local delegate = fgui_internal.GetDelegate(func, obj, true, FairyGUI.TimerCallback)
FairyGUI.Timers.inst:Add(interval, repeatCount, delegate, callbackParam)
end
function fgui.remove_timer(func, obj)
local delegate = fgui_internal.GetDelegate(func, obj, false)
if delegate~=nil then
FairyGUI.Timers.inst:Remove(delegate)
end
end
---以下是内部使用的代码---
fgui_internal = {}
--这里建立一个c# delegate到lua函数的映射,是为了支持self参数,和方便地进行remove操作
fgui_internal.EventDelegates = {}
setmetatable(fgui_internal.EventDelegates, {__mode = &#34;k&#34;})
function fgui_internal.GetDelegate(func, obj, createIfNone, delegateType)
local mapping
if obj~=nil then
mapping = obj.EventDelegates
if mapping==nil then
mapping = {}
setmetatable(mapping, {__mode = &#34;k&#34;})
obj.EventDelegates = mapping
end
else
mapping = fgui_internal.EventDelegates
end
local delegate = mapping[func]
if createIfNone and delegate==nil then
local realFunc
if obj~=nil then
realFunc = function(context)
func(obj,context)
end
else
realFunc = func
end
delegateType = delegateType or FairyGUI.EventCallback1
delegate = delegateType(realFunc)
mapping[func] = delegate
end
return delegate
end
--将EventListener.Add和EventListener.Remove重新进行定义,以适应lua的使用习惯
local EventListener_mt = getmetatable(EventListener)
local oldListenerAdd = rawget(EventListener_mt, &#39;Add&#39;)
local oldListenerRemove = rawget(EventListener_mt, &#39;Remove&#39;)
local oldListenerSet = rawget(EventListener_mt, &#39;Set&#39;)
local oldListenerAddCapture = rawget(EventListener_mt, &#39;AddCapture&#39;)
local oldListenerRemoveCapture = rawget(EventListener_mt, &#39;RemoveCapture&#39;)
local function ListenerAdd(listener, func, obj)
local delegate = fgui_internal.GetDelegate(func, obj, true)
oldListenerAdd(listener, delegate)
end
local function ListenerRemove(listener, func, obj)
local delegate = fgui_internal.GetDelegate(func, obj, false)
if delegate ~= nil then
oldListenerRemove(listener, delegate)
end
end
local function ListenerSet(listener, func, obj)
if func==nil then
oldListenerSet(listener, nil)
else
local delegate = fgui_internal.GetDelegate(func, obj, true)
oldListenerSet(listener, delegate)
end
end
local function ListenerAddCapture(listener, func, obj)
local delegate = fgui_internal.GetDelegate(func, obj, true)
oldListenerAddCapture(listener, delegate)
end
local function ListenerRemoveCapture(listener, func, obj)
local delegate = fgui_internal.GetDelegate(func, obj, false)
if delegate ~= nil then
oldListenerRemoveCapture(listener, delegate)
end
end
rawset(EventListener_mt, &#39;Add&#39;, ListenerAdd)
rawset(EventListener_mt, &#39;Remove&#39;, ListenerRemove)
rawset(EventListener_mt, &#39;Set&#39;, ListenerSet)
rawset(EventListener_mt, &#39;AddCapture&#39;, ListenerAddCapture)
rawset(EventListener_mt, &#39;RemoveCapture&#39;, ListenerRemoveCapture)
--将EventDispatcher.AddEventListener和EventDispatcher.RemoveEventListener重新进行定义,以适应lua的使用习惯
local EventDispatcher_mt = getmetatable(EventDispatcher)
local oldAddEventListener = rawget(EventDispatcher_mt, &#39;AddEventListener&#39;)
local oldRemoveEventListener = rawget(EventDispatcher_mt, &#39;RemoveEventListener&#39;)
local function AddEventListener(dispatcher, name, func, obj)
local delegate = fgui_internal.GetDelegate(func, obj, true)
oldAddEventListener(dispatcher, name, delegate)
end
local function RemoveEventListener(dispatcher, name, func, obj)
local delegate = fgui_internal.GetDelegate(func, obj, false)
if delegate ~= nil then
oldRemoveEventListener(dispatcher, name, delegate)
end
end
rawset(EventDispatcher_mt, &#39;AddEventListener&#39;, AddEventListener)
rawset(EventDispatcher_mt, &#39;RemoveEventListener&#39;, RemoveEventListener)然后将fairyGUI需要用到的api添加到xlua的生成列表中,重新生成一下warp文件。
//lua中要使用到C#库的配置,比如C#标准库,或者Unity API,第三方库等。
[LuaCallCSharp]
public static List<Type> LuaCallCSharp = new List<Type>() {
//FGUI
typeof(EventContext),
typeof(EventDispatcher),
typeof(EventListener),
typeof(InputEvent),
typeof(DisplayObject),
typeof(Container),
typeof(Stage),
typeof(FairyGUI.Controller),
typeof(GObject),
typeof(GGraph),
typeof(GGroup),
typeof(GImage),
typeof(GLoader),
typeof(GMovieClip),
typeof(TextFormat),
typeof(GTextField),
typeof(GRichTextField),
typeof(GTextInput),
typeof(GComponent),
typeof(GList),
typeof(GRoot),
typeof(GLabel),
typeof(GButton),
typeof(GComboBox),
typeof(GProgressBar),
typeof(GSlider),
typeof(PopupMenu),
typeof(ScrollPane),
typeof(Transition),
typeof(UIPackage),
typeof(Window),
typeof(GObjectPool),
typeof(Relations),
typeof(RelationType),
typeof(Timers),
typeof(GTween),
typeof(GTweener),
typeof(EaseType),
typeof(TweenValue),
typeof(UIObjectFactory),
typeof(LuaUIHelper),
typeof(GLuaComponent),
typeof(GLuaLabel),
typeof(GLuaButton),
typeof(GLuaProgressBar),
typeof(GLuaSlider),
typeof(GLuaComboBox),
typeof(LuaWindow),
typeof(GoWrapper)
};重新生成后就可以在lua中调用fgui的api了;
下面是lua如何继承c#的window基类的代码(结合自动发布lua代码使用更香)
WindowBase = fgui.window_class()
--构建函数
function WindowBase:ctor()
CS.UnityEngine.Debug.LogWarning(&#34;WindowBasector&#34;)
end
--可覆盖的函数(可选,不是说必须)
function WindowBase:OnInit()
CS.UnityEngine.Debug.LogWarning(&#34;WindowBaseonInit&#34;)
self.contentPane = UIPackage.CreateObject(&#34;LoginPanelPackage&#34;, &#34;LoginPanel&#34;);
end
function WindowBase:OnShown()
CS.UnityEngine.Debug.LogWarning(&#34;WindowBaseonShown&#34;)
end
function WindowBase:OnHide()
CS.UnityEngine.Debug.LogWarning(&#34;WindowBaseonHide&#34;)
end
--创建并显示窗口
local win = WindowBase.New()
win:Show()
-------------------------------------------------------------
--加载LoginPanel
UIPackage.AddPackage(&#34;Assets/_Resource/UI/FguiRes/LoginPanelPackage&#34;);
local view = UI_LoginPanel:CreateInstance()
GRoot.inst:AddChild(view)
UI_LoginPanel.m_n9.onClick:Add(function()
CS.UnityEngine.Debug.LogWarning(&#34;登录成功&#34;)
end);
require(&#34;UIScripts._Core.WindowBase&#34;)FairyGUI自动导出lua脚本
这边分享一个fgui的插件 FairyGUI_GenCode_Lua,克隆这个项目到 FairyGUI 工程的 plugins 目录中,刷新 FairyGUI 编辑器插件窗口或重启编辑器。
这个插件主要需要看三个脚本: - main.lua
(导出lua脚本的主要逻辑,可以根据你自己的项目自定义逻辑,非常方便)
(绑定脚本的模板,里面的变量可自定义,根据main.lua里的逻辑,导出时会自动替换)
(组件脚本的模板,里面的变量可自定义,根据main.lua里的逻辑,导出时会自动替换)
总结
fgui相较于其他的ui插件,最大的优势就在于开发效率高,拼UI界面可以交给美术来完成,程序只需要负责具体的业务逻辑即可,省去了ui开发里大部分的工作,特别是到项目中期,经常会出现ui改版的情况,之前项目使用的是ngui,每次美术有新的idea,受苦的总是我们,现在终于能将这个工作完整的托付给美术了,当然相较于其他UI插件,fairyGUI还需要对美术进行培训,但我认为相较于效率的提高,美术大大们也会欣然接受这个插件的。 |
|