找回密码
 立即注册
查看: 267|回复: 1

项目篇-FairyGui预研(自动导出lua脚本、与xlua的集成)

[复制链接]
发表于 2021-8-10 18:48 | 显示全部楼层 |阅读模式
前言:上个项目终于告一段落了,进入了内测阶段,趁着这段空闲时间,主程让我这边预研一下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 'xlua.util'



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("包名","组件名"), 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("invalid extension base: "..base)
        return
    end
    FairyGUI.LuaUIHelper.SetExtension(url, typeof(base), extension.Extend)
end

--[[
用于继承FairyGUI原来的组件类,例如
MyComponent = fgui.extension_class(GComponent)
function MyComponent:ctor() --当组件构建完成时此方法被调用
print(self:GetChild("n1"))
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 = "k"})
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 = "k"})
            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, 'Add')
local oldListenerRemove = rawget(EventListener_mt, 'Remove')
local oldListenerSet = rawget(EventListener_mt, 'Set')
local oldListenerAddCapture = rawget(EventListener_mt, 'AddCapture')
local oldListenerRemoveCapture = rawget(EventListener_mt, 'RemoveCapture')

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, 'Add', ListenerAdd)
rawset(EventListener_mt, 'Remove', ListenerRemove)
rawset(EventListener_mt, 'Set', ListenerSet)
rawset(EventListener_mt, 'AddCapture', ListenerAddCapture)
rawset(EventListener_mt, 'RemoveCapture', ListenerRemoveCapture)

--将EventDispatcher.AddEventListener和EventDispatcher.RemoveEventListener重新进行定义,以适应lua的使用习惯
local EventDispatcher_mt = getmetatable(EventDispatcher)
local oldAddEventListener = rawget(EventDispatcher_mt, 'AddEventListener')
local oldRemoveEventListener = rawget(EventDispatcher_mt, 'RemoveEventListener')

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, 'AddEventListener', AddEventListener)
rawset(EventDispatcher_mt, 'RemoveEventListener', 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("WindowBasector")
end

--可覆盖的函数(可选,不是说必须)
function WindowBase:OnInit()
    CS.UnityEngine.Debug.LogWarning("WindowBaseonInit")
    self.contentPane = UIPackage.CreateObject("LoginPanelPackage", "LoginPanel");
end

function WindowBase:OnShown()
    CS.UnityEngine.Debug.LogWarning("WindowBaseonShown")
end

function WindowBase:OnHide()
    CS.UnityEngine.Debug.LogWarning("WindowBaseonHide")
end

--创建并显示窗口
local win = WindowBase.New()
win:Show()
-------------------------------------------------------------

--加载LoginPanel
UIPackage.AddPackage("Assets/_Resource/UI/FguiRes/LoginPanelPackage");
local view = UI_LoginPanel:CreateInstance()
GRoot.inst:AddChild(view)
UI_LoginPanel.m_n9.onClick:Add(function()
        CS.UnityEngine.Debug.LogWarning("登录成功")

    end);

require("UIScripts._Core.WindowBase")FairyGUI自动导出lua脚本

这边分享一个fgui的插件  FairyGUI_GenCode_Lua,克隆这个项目到 FairyGUI 工程的 plugins 目录中,刷新 FairyGUI 编辑器插件窗口或重启编辑器。
这个插件主要需要看三个脚本: - main.lua
(导出lua脚本的主要逻辑,可以根据你自己的项目自定义逻辑,非常方便)
    binder_template.txt
(绑定脚本的模板,里面的变量可自定义,根据main.lua里的逻辑,导出时会自动替换)
    component_template.txt
(组件脚本的模板,里面的变量可自定义,根据main.lua里的逻辑,导出时会自动替换)
总结

fgui相较于其他的ui插件,最大的优势就在于开发效率高,拼UI界面可以交给美术来完成,程序只需要负责具体的业务逻辑即可,省去了ui开发里大部分的工作,特别是到项目中期,经常会出现ui改版的情况,之前项目使用的是ngui,每次美术有新的idea,受苦的总是我们,现在终于能将这个工作完整的托付给美术了,当然相较于其他UI插件,fairyGUI还需要对美术进行培训,但我认为相较于效率的提高,美术大大们也会欣然接受这个插件的。
发表于 2021-8-10 18:51 | 显示全部楼层
带哥好强[赞同]
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 04:58 , Processed in 0.089896 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表