跳转至

SOUI中的Lua脚本开发

本文介绍如何在SOUI中使用Lua脚本进行界面开发,包括基本用法、C++对象导出到Lua以及实现类似Web开发的开发模式。

脚本系统概述

SOUI的脚本系统基于Lua 5.2.3,使用lua_tinker实现C++对象的导出。主要特性包括:

  1. 基本功能
  2. 使用XML描述界面布局
  3. 使用Lua脚本处理界面逻辑
  4. 动态更新界面和逻辑
  5. 实现类似Web开发的体验

  6. C++集成

  7. C++代码可以调用Lua函数
  8. Lua脚本可以访问C++对象
  9. 支持事件处理和回调
  10. 支持对象继承关系

  11. 接口设计

    struct IScriptModule : public IObjRef
    {
        // 获取脚本引擎
        virtual void* GetScriptEngine() = 0;
    
        // 执行脚本文件
        virtual void executeScriptFile(LPCSTR pszScriptFile) = 0;
    
        // 执行脚本缓冲区
        virtual void executeScriptBuffer(const char* buff, size_t sz) = 0;
    
        // 执行脚本字符串
        virtual void executeString(LPCSTR str) = 0;
    
        // 执行事件处理器
        virtual bool executeScriptedEventHandler(LPCSTR handler_name,
            EventArgs *pEvt) = 0;
    };
    

启用Lua支持

1. 加载Lua模块

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE, LPTSTR, int)
{
    SComMgr *pComMgr = new SComMgr;
    {
        // 创建SOUI应用程序
        SApplication *theApp = new SApplication(pRenderFactory, hInstance);

        #ifdef DLL_CORE
        // 加载Lua脚本模块
        bool bLoaded = pComMgr->CreateScrpit_Lua((IObjRef**)&pScriptLua);
        SASSERT_FMT(bLoaded, _T("load interface [%s] failed!"), 
            _T("scirpt_lua"));
        theApp->SetScriptFactory(pScriptLua);
        #endif

        // ...其他初始化代码...
    }
    delete pComMgr;
    return 0;
}

2. 在XML中添加脚本

<SOUI>
    <script src="lua:game_logic">
    <![CDATA[
    -- Lua脚本代码
    function on_init(args)
        -- 初始化代码
    end

    function on_exit(args)
        -- 退出清理代码
    end

    function on_button_click(args)
        -- 按钮点击处理
        return 1
    end
    ]]>
    </script>

    <root on_init="on_init" on_exit="on_exit">
        <!-- 界面布局 -->
    </root>
</SOUI>

脚本编写

1. 基本结构

-- 全局变量定义
local win = nil
local main_wnd = nil

-- 初始化函数
function on_init(args)
    win = toHostWnd(args.sender)
    main_wnd = win:GetRoot()
end

-- 退出函数
function on_exit(args)
    -- 清理代码
end

-- 事件处理函数
function on_button_click(args)
    local btn = toSWindow(args.sender)
    -- 处理按钮点击
    return 1
end

2. 常用操作

-- 查找控件
local button = win:FindChildByNameA("btn_test", -1)

-- 修改属性
button:SetAttribute("text", "新文本")

-- 设置可见性
button:SetVisible(1, 1)

-- 移动位置
local rc = button:GetWindowRect2()
rc:OffsetRect(10, 10)
button:Move(rc)

-- 定时器操作
local timer_id = win:setInterval("on_timer", 1000)
win:clearTimer(timer_id)

3. 事件处理

-- 点击事件
function on_button_click(args)
    local sender = toSWindow(args.sender)
    -- 处理点击
    return 1
end

-- 大小改变事件
function on_size_changed(args)
    local rc = sender:GetWindowRect2()
    -- 处理大小改变
    return 1
end

-- 定时器事件
function on_timer(args)
    -- 处理定时器
end

实例:跑马游戏

1. 界面布局

<include>
    <window size="full,full" name="game_wnd" on_size="on_canvas_size">
        <!-- 游戏场景 -->
        <window name="game_canvas" clipClient="1">
            <!-- 马匹 -->
            <gifplayer name="player_1" float="1" skin="gif_horse">
                <text pos="0,0" font="size:20">1</text>
            </gifplayer>
            <!-- 更多马匹... -->
        </window>

        <!-- 控制按钮 -->
        <window name="game_toolbar">
            <button name="btn_run" on_command="on_run">开始</button>
            <text name="txt_coins">金币: 100</text>
        </window>
    </window>
</include>

2. 游戏逻辑

-- 全局变量
local game_state = {
    coins = 100,
    bet = {0,0,0,0},
    running = false
}

-- 初始化
function on_init(args)
    win = toHostWnd(args.sender)
    game_wnd = win:FindChildByNameA("game_wnd", -1)
    -- 初始化其他控件...
end

-- 开始/停止游戏
function on_run(args)
    if not game_state.running then
        -- 开始游戏
        game_state.running = true
        start_race()
    else
        -- 停止游戏
        stop_race()
    end
    return 1
end

-- 下注处理
function on_bet(args)
    local btn = toSWindow(args.sender)
    local horse_id = btn:GetID()

    if game_state.coins >= 10 then
        game_state.coins = game_state.coins - 10
        game_state.bet[horse_id] = game_state.bet[horse_id] + 10
        update_ui()
    end
    return 1
end

高级用法

1. 模块化开发

-- 游戏逻辑模块
local game = {
    init = function(wnd)
        -- 初始化游戏
    end,

    start = function()
        -- 开始游戏
    end,

    stop = function()
        -- 停止游戏
    end
}

-- 使用模块
function on_init(args)
    game.init(toHostWnd(args.sender))
end

2. 动态UI生成

function create_dynamic_ui()
    local parent = win:FindChildByNameA("container", -1)
    -- 创建按钮
    parent:CreateChildrenFromString([[
        <button pos="0,0,100,30" on_command="on_dynamic_click">
            动态按钮
        </button>
    ]])
end

3. 数据绑定

function bind_data()
    -- 创建数据模型
    local model = {
        score = 0,
        level = 1
    }

    -- 更新UI
    function update_ui()
        local txt_score = win:FindChildByNameA("txt_score", -1)
        txt_score:SetWindowText(T(model.score))
    end

    -- 数据变化时更新UI
    function model:set_score(value)
        self.score = value
        update_ui()
    end
end

最佳实践

1. 脚本组织

-- config.lua:配置
local config = {
    GAME_TIME = 60,
    MAX_SCORE = 1000
}

-- ui.lua:UI操作
local ui = {
    init = function()
        -- UI初始化
    end
}

-- game.lua:游戏逻辑
local game = {
    start = function()
        -- 游戏逻辑
    end
}

2. 错误处理

function safe_call(func, ...)
    local status, err = pcall(func, ...)
    if not status then
        -- 错误处理
        print("Error:", err)
    end
end

3. 性能优化

-- 缓存频繁使用的对象
local cached_controls = {}

function get_control(name)
    if not cached_controls[name] then
        cached_controls[name] = win:FindChildByNameA(name, -1)
    end
    return cached_controls[name]
end

注意事项

  1. 内存管理
  2. 及时清理不用的定时器
  3. 避免循环引用
  4. 注意变量作用域

  5. 错误处理

  6. 使用pcall捕获错误
  7. 添加错误日志
  8. 提供错误恢复机制

  9. 性能考虑

  10. 缓存频繁使用的对象
  11. 避免频繁的字符串操作
  12. 合理使用定时器