Unity引擎与Lua脚本在车机HMI开发中的深度融合实践

Unity引擎与Lua脚本在车机HMI开发中的深度融合实践

第一部分:引言与架构设计

1.1 Unity引擎与Lua结合的核心优势

在智能座舱HMI开发领域,Unity引擎与Lua脚本的结合已经成为行业主流技术方案之一。这种架构设计充分发挥了双方的优势:

  • Unity引擎:提供业界领先的3D渲染能力、成熟的UI系统(UGUI/UI Toolkit)、强大的动画系统和跨平台部署能力,能够实现"游戏级"的视觉体验
  • Lua脚本:作为轻量级、易嵌入的脚本语言,具有快速热更新、语法简洁、执行效率高的特点,特别适合处理业务逻辑

这种组合的核心价值在于解耦渲染层与逻辑层,实现业务逻辑的热更新而不必重新编译整个Unity工程,这对于需要频繁迭代的车机系统至关重要。

1.2 架构设计总览

典型的Unity+Lua车机HMI架构分为四个层次:

┌─────────────────────────────────────────────┐
│             应用层(Lua逻辑层)              │
│  - UI业务逻辑  - 动画控制  - 数据管理       │
├─────────────────────────────────────────────┤
│           框架层(C#桥接层)                │
│  - Lua虚拟机管理 - 事件系统 - 资源管理      │
├─────────────────────────────────────────────┤
│           渲染层(Unity引擎层)             │
│  - 3D渲染  - UI渲染  - 动画系统             │
├─────────────────────────────────────────────┤
│           系统层(车机原生层)              │
│  - Android/Linux - CAN总线 - 系统服务       │
└─────────────────────────────────────────────┘

第二部分:Unity与Lua集成方案

2.1 Lua集成框架选择

目前主流有两种Lua集成方案:

  1. XLua:腾讯开源的Lua框架,支持热补丁技术
  2. ToLua:传统成熟的Lua框架,性能稳定

以下以XLua为例展示集成方法:

// XLuaManager.cs - C#侧Lua管理器
using UnityEngine;
using XLua;

public class XLuaManager : Singleton<XLuaManager>
{
    private LuaEnv luaEnv;
    private LuaTable mainLuaTable;
    
    void Start()
    {
        // 初始化Lua环境
        luaEnv = new LuaEnv();
        luaEnv.AddLoader(CustomLoader);
        
        // 设置全局错误处理
        luaEnv.Global.Set("__G", luaEnv.Global);
        luaEnv.DoString(@"
            xlua.hotfix = function(cs, field, func)
                if func == nil then
                    xlua.private_accessible(cs)
                    local function func(...)
                        return nil
                    end
                end
                xlua.hotfix(cs, field, func)
            end
            
            -- 重定向print到Unity日志
            local old_print = print
            print = function(...)
                local args = {...}
                local str = ''
                for i = 1, select('#', ...) do
                    str = str .. tostring(args[i]) .. '\t'
                end
                CS.UnityEngine.Debug.Log('[LUA] ' .. str)
                old_print(...)
            end
        ");
        
        // 加载主Lua模块
        LoadLuaModule("Main");
    }
    
    // 自定义加载器,支持从多个路径加载Lua文件
    private byte[] CustomLoader(ref string filepath)
    {
        // 1. 优先从热更新目录加载
        string hotfixPath = Path.Combine(Application.persistentDataPath, 
                                       "LuaHotfix", filepath + ".lua");
        if (File.Exists(hotfixPath))
        {
            return File.ReadAllBytes(hotfixPath);
        }
        
        // 2. 从AssetBundle加载
        TextAsset luaAsset = LoadLuaFromAssetBundle(filepath);
        if (luaAsset != null)
        {
            return luaAsset.bytes;
        }
        
        // 3. 从Resources加载(开发阶段)
        string resourcePath = "LuaScripts/" + filepath.Replace('.', '/');
        luaAsset = Resources.Load<TextAsset>(resourcePath);
        if (luaAsset != null)
        {
            return luaAsset.bytes;
        }
        
        return null;
    }
    
    // 加载Lua模块并执行
    public LuaTable LoadLuaModule(string moduleName)
    {
        try
        {
            luaEnv.DoString($"require '{moduleName}'");
            mainLuaTable = luaEnv.Global.Get<LuaTable>(moduleName);
            return mainLuaTable;
        }
        catch (System.Exception e)
        {
            Debug.LogError($"加载Lua模块失败: {moduleName}, 错误: {e.Message}");
            return null;
        }
    }
    
    // 调用Lua函数
    public object[] CallLuaFunction(string funcName, params object[] args)
    {
        if (mainLuaTable == null)
        {
            Debug.LogError("Lua模块未加载");
            return null;
        }
        
        LuaFunction func = mainLuaTable.Get<LuaFunction>(funcName);
        if (func != null)
        {
            return func.Call(args);
        }
        
        Debug.LogWarning($"Lua函数不存在: {funcName}");
        return null;
    }
    
    void Update()
    {
        // Lua垃圾回收
        if (luaEnv != null)
        {
            luaEnv.Tick();
        }
    }
    
    void OnDestroy()
    {
        if (luaEnv != null)
        {
            luaEnv.Dispose();
            luaEnv = null;
        }
    }
}

2.2 C#与Lua双向通信机制

// Bridge/CSharpToLuaBridge.cs - C#到Lua的通信桥
using UnityEngine;
using XLua;

[LuaCallCSharp]
public class CSharpToLuaBridge : MonoBehaviour
{
    // 注册C#事件到Lua回调
    public void RegisterEvent(string eventName, LuaFunction callback)
    {
        EventManager.Instance.AddListener(eventName, 
            (data) => {
                if (callback != null)
                {
                    callback.Call(data);
                }
            });
    }
    
    // 调用Lua的UI控制器
    public void CallLuaUIController(string uiName, string method, params object[] args)
    {
        XLuaManager.Instance.CallLuaFunction("UIController." + method, 
            new object[] { uiName }.Concat(args).ToArray());
    }
    
    // 车辆数据更新接口
    public void UpdateVehicleData(string dataType, object value)
    {
        XLuaManager.Instance.CallLuaFunction("VehicleDataManager.UpdateData", 
            dataType, value);
    }
}

// 车辆数据模型(供Lua访问)
[LuaCallCSharp]
public class VehicleDataModel
{
    public float Speed { get; set; }
    public float BatteryLevel { get; set; }
    public int GearPosition { get; set; }
    public float OutsideTemperature { get; set; }
    public bool LeftDoorOpen { get; set; }
    public bool RightDoorOpen { get; set; }
    
    // 序列化为Lua表
    public LuaTable ToLuaTable(LuaEnv luaEnv)
    {
        LuaTable table = luaEnv.NewTable();
        table.Set("Speed", Speed);
        table.Set("BatteryLevel", BatteryLevel);
        table.Set("GearPosition", GearPosition);
        table.Set("OutsideTemperature", OutsideTemperature);
        table.Set("LeftDoorOpen", LeftDoorOpen);
        table.Set("RightDoorOpen", RightDoorOpen);
        return table;
    }
}

第三部分:Lua处理业务逻辑

3.1 业务逻辑架构设计

-- Main.lua - 主入口模块
local Main = {}

-- 模块初始化
function Main.Init()
    print("[LUA] Main模块初始化")
    
    -- 初始化子模块
    require "EventManager"
    require "UIController"
    require "VehicleDataManager"
    require "AnimationController"
    require "NetworkManager"
    
    -- 注册事件监听
    EventManager.AddListener("VehicleDataUpdate", Main.OnVehicleDataUpdate)
    EventManager.AddListener("UserInput", Main.OnUserInput)
    EventManager.AddListener("SystemCommand", Main.OnSystemCommand)
    
    -- 初始化UI
    UIController.Init()
    
    -- 启动数据更新循环
    Main.StartUpdateLoop()
end

-- 车辆数据更新回调
function Main.OnVehicleDataUpdate(data)
    -- 数据验证
    if not Main.ValidateVehicleData(data) then
        print("警告:车辆数据无效")
        return
    end
    
    -- 更新数据管理器
    VehicleDataManager.Update(data)
    
    -- 通知UI更新
    UIController.OnDataChanged(data.type, data.value)
    
    -- 触发业务规则
    Main.CheckBusinessRules(data)
end

-- 数据验证
function Main.ValidateVehicleData(data)
    if data == nil then return false end
    
    -- 速度范围验证 (0-300 km/h)
    if data.type == "Speed" then
        return data.value >= 0 and data.value <= 300
    end
    
    -- 电量范围验证 (0-100%)
    if data.type == "BatteryLevel" then
        return data.value >= 0 and data.value <= 100
    end
    
    return true
end

-- 业务规则检查
function Main.CheckBusinessRules(data)
    -- 低电量警告
    if data.type == "BatteryLevel" and data.value < 20 then
        Main.ShowLowBatteryWarning(data.value)
    end
    
    -- 超速警告
    if data.type == "Speed" and data.value > 120 then
        Main.ShowOverSpeedWarning(data.value)
    end
    
    -- 车门未关提醒
    if data.type == "DoorStatus" and data.value == "Open" then
        Main.ShowDoorOpenWarning()
    end
end

-- 低电量警告
function Main.ShowLowBatteryWarning(level)
    local message = string.format("电量不足: %.0f%%,请及时充电", level)
    UIController.ShowNotification("warning", message, 5000)
    
    -- 记录日志
    EventManager.Dispatch("SystemLog", {
        level = "WARN",
        message = message,
        timestamp = os.time()
    })
end

-- 用户输入处理
function Main.OnUserInput(input)
    print("收到用户输入:", input.action, input.value)
    
    -- 路由到对应的处理器
    if input.action == "ButtonClick" then
        Main.OnButtonClick(input.elementId, input.value)
    elseif input.action == "TouchGesture" then
        Main.OnTouchGesture(input.gestureType, input.params)
    elseif input.action == "VoiceCommand" then
        Main.OnVoiceCommand(input.command)
    end
end

-- 系统指令处理
function Main.OnSystemCommand(command)
    print("收到系统指令:", command.type)
    
    if command.type == "SwitchTheme" then
        UIController.SwitchTheme(command.themeName)
    elseif command.type == "ChangeLanguage" then
        Main.ChangeLanguage(command.language)
    elseif command.type == "EnterSleepMode" then
        Main.EnterSleepMode()
    end
end

-- 启动更新循环
function Main.StartUpdateLoop()
    -- 使用协程实现定时更新
    local updateCoroutine = coroutine.create(function()
        while true do
            -- 每秒更新一次UI数据
            Main.UpdateUIData()
            coroutine.wait(1.0)  -- 自定义等待函数
        end
    end)
    
    coroutine.resume(updateCoroutine)
end

-- UI数据更新
function Main.UpdateUIData()
    local currentData = VehicleDataManager.GetCurrentData()
    
    -- 更新仪表盘
    UIController.UpdateInstrumentCluster({
        speed = currentData.speed,
        rpm = currentData.rpm,
        battery = currentData.batteryLevel,
        range = currentData.estimatedRange
    })
    
    -- 更新状态栏
    UIController.UpdateStatusBar({
        time = os.date("%H:%M"),
        temperature = currentData.outsideTemperature,
        network = NetworkManager.GetSignalStrength()
    })
end

return Main

3.2 车辆数据管理器

-- VehicleDataManager.lua - 车辆数据管理
local VehicleDataManager = {}
local _dataCache = {}  -- 数据缓存
local _listeners = {}   -- 数据监听器
local _dataHistory = {} -- 数据历史记录

-- 初始化
function VehicleDataManager.Init()
    _dataCache = {
        speed = 0,
        batteryLevel = 100,
        gearPosition = "P",
        outsideTemperature = 25,
        leftDoorOpen = false,
        rightDoorOpen = false,
        estimatedRange = 500,
        tirePressure = {240, 240, 240, 240}
    }
    
    -- 初始化历史记录
    _dataHistory.speed = {}
    _dataHistory.batteryLevel = {}
    
    print("[LUA] VehicleDataManager 初始化完成")
end

-- 更新数据
function VehicleDataManager.Update(dataType, value, timestamp)
    if _dataCache[dataType] == nil then
        print("警告:未知的数据类型:", dataType)
        return
    end
    
    -- 存储历史数据(用于图表显示)
    table.insert(_dataHistory[dataType], {
        value = value,
        time = timestamp or os.time()
    })
    
    -- 限制历史记录长度
    if #_dataHistory[dataType] > 1000 then
        table.remove(_dataHistory[dataType], 1)
    end
    
    -- 更新缓存
    local oldValue = _dataCache[dataType]
    _dataCache[dataType] = value
    
    -- 通知监听者
    VehicleDataManager.NotifyListeners(dataType, value, oldValue)
    
    -- 触发派生数据计算
    VehicleDataManager.CalculateDerivedData(dataType, value)
end

-- 批量更新
function VehicleDataManager.BatchUpdate(dataTable)
    for dataType, value in pairs(dataTable) do
        VehicleDataManager.Update(dataType, value)
    end
end

-- 通知监听者
function VehicleDataManager.NotifyListeners(dataType, newValue, oldValue)
    if _listeners[dataType] then
        for _, listener in ipairs(_listeners[dataType]) do
            -- 异步调用监听器,避免阻塞
            coroutine.resume(coroutine.create(function()
                listener(newValue, oldValue)
            end))
        end
    end
end

-- 注册数据监听
function VehicleDataManager.AddListener(dataType, callback)
    if not _listeners[dataType] then
        _listeners[dataType] = {}
    end
    
    table.insert(_listeners[dataType], callback)
end

-- 移除数据监听
function VehicleDataManager.RemoveListener(dataType, callback)
    if not _listeners[dataType] then return end
    
    for i, cb in ipairs(_listeners[dataType]) do
        if cb == callback then
            table.remove(_listeners[dataType], i)
            break
        end
    end
end

-- 获取当前数据
function VehicleDataManager.GetCurrentData(dataType)
    if dataType then
        return _dataCache[dataType]
    else
        return _dataCache
    end
end

-- 获取历史数据
function VehicleDataManager.GetHistoryData(dataType, count)
    if not _dataHistory[dataType] then return {} end
    
    count = count or 100
    local startIndex = math.max(1, #_dataHistory[dataType] - count + 1)
    local result = {}
    
    for i = startIndex, #_dataHistory[dataType] do
        table.insert(result, _dataHistory[dataType][i])
    end
    
    return result
end

-- 计算派生数据
function VehicleDataManager.CalculateDerivedData(dataType, value)
    -- 根据速度计算能耗
    if dataType == "speed" then
        VehicleDataManager.CalculateEnergyConsumption(value)
    end
    
    -- 根据电量计算续航
    if dataType == "batteryLevel" then
        VehicleDataManager.CalculateEstimatedRange(value)
    end
end

-- 计算能耗
function VehicleDataManager.CalculateEnergyConsumption(speed)
    -- 简化能耗模型:速度越快,能耗越高
    local consumptionRate
    if speed < 60 then
        consumptionRate = 0.15  -- 15Wh/km
    elseif speed < 100 then
        consumptionRate = 0.18  -- 18Wh/km
    else
        consumptionRate = 0.22  -- 22Wh/km
    end
    
    _dataCache.energyConsumption = consumptionRate
    
    -- 通知UI更新
    EventManager.Dispatch("DerivedDataUpdate", {
        type = "EnergyConsumption",
        value = consumptionRate
    })
end

-- 计算续航里程
function VehicleDataManager.CalculateEstimatedRange(batteryLevel)
    -- 简化计算:剩余续航 = 电池容量 * 电池百分比 / 当前能耗
    local batteryCapacity = 75  -- kWh,假设电池容量75度
    local usableCapacity = batteryCapacity * batteryLevel / 100
    
    if _dataCache.energyConsumption then
        local range = usableCapacity / _dataCache.energyConsumption
        _dataCache.estimatedRange = math.floor(range)
        
        EventManager.Dispatch("DerivedDataUpdate", {
            type = "EstimatedRange",
            value = _dataCache.estimatedRange
        })
    end
end

-- 数据序列化(用于保存或传输)
function VehicleDataManager.Serialize()
    return {
        cache = _dataCache,
        timestamp = os.time()
    }
end

-- 数据反序列化
function VehicleDataManager.Deserialize(data)
    if data and data.cache then
        _dataCache = data.cache
        return true
    end
    return false
end

return VehicleDataManager

第四部分:Lua控制UI动画

4.1 UI动画控制器

-- AnimationController.lua - UI动画控制
local AnimationController = {}
local _animationQueue = {}      -- 动画队列
local _runningAnimations = {}   -- 正在运行的动画
local _animationCallbacks = {}  -- 动画回调

-- 初始化
function AnimationController.Init()
    print("[LUA] AnimationController 初始化")
    
    -- 注册到事件系统
    EventManager.AddListener("UIAnimationRequest", AnimationController.OnAnimationRequest)
end

-- 播放UI动画
function AnimationController.PlayAnimation(animName, params, callback)
    print("播放动画:", animName)
    
    -- 查找动画配置
    local animConfig = AnimationController.GetAnimationConfig(animName)
    if not animConfig then
        print("警告:动画配置不存在:", animName)
        if callback then callback(false) end
        return
    end
    
    -- 创建动画实例
    local animation = {
        name = animName,
        config = animConfig,
        params = params or {},
        startTime = os.clock(),
        progress = 0,
        state = "waiting",
        callback = callback
    }
    
    -- 检查是否允许同时播放多个相同动画
    if not animConfig.allowMultiple then
        for i, anim in ipairs(_runningAnimations) do
            if anim.name == animName then
                if animConfig.queuePolicy == "replace" then
                    -- 替换现有动画
                    AnimationController.StopAnimation(anim.name, "replaced")
                    break
                elseif animConfig.queuePolicy == "ignore" then
                    -- 忽略新请求
                    if callback then callback(false, "ignored") end
                    return
                end
            end
        end
    end
    
    -- 添加到队列或立即执行
    if animConfig.delay and animConfig.delay > 0 then
        table.insert(_animationQueue, animation)
    else
        AnimationController.StartAnimation(animation)
    end
end

-- 开始动画
function AnimationController.StartAnimation(animation)
    animation.state = "running"
    animation.startTime = os.clock()
    
    table.insert(_runningAnimations, animation)
    
    -- 通过C#调用Unity动画系统
    CS.CSharpToLuaBridge.Instance.StartUIAnimation(
        animation.name,
        animation.params,
        animation.config.duration
    )
    
    -- 注册完成回调
    _animationCallbacks[animation.name] = function(success)
        AnimationController.OnAnimationComplete(animation, success)
    end
    
    -- 触发动画开始事件
    EventManager.Dispatch("AnimationStarted", {
        name = animation.name,
        params = animation.params
    })
end

-- 停止动画
function AnimationController.StopAnimation(animName, reason)
    for i, anim in ipairs(_runningAnimations) do
        if anim.name == animName then
            anim.state = "stopped"
            
            -- 通过C#停止Unity动画
            CS.CSharpToLuaBridge.Instance.StopUIAnimation(animName)
            
            -- 执行回调
            if anim.callback then
                anim.callback(false, reason or "stopped")
            end
            
            table.remove(_runningAnimations, i)
            
            EventManager.Dispatch("AnimationStopped", {
                name = animName,
                reason = reason
            })
            
            break
        end
    end
end

-- 动画完成回调
function AnimationController.OnAnimationComplete(animation, success)
    for i, anim in ipairs(_runningAnimations) do
        if anim == animation then
            anim.state = success and "completed" or "failed"
            
            -- 执行回调
            if anim.callback then
                anim.callback(success)
            end
            
            table.remove(_runningAnimations, i)
            
            -- 触发完成事件
            EventManager.Dispatch("AnimationCompleted", {
                name = anim.name,
                success = success,
                duration = os.clock() - anim.startTime
            })
            
            break
        end
    end
end

-- 更新循环(由主模块调用)
function AnimationController.Update(deltaTime)
    -- 处理动画队列
    for i = #_animationQueue, 1, -1 do
        local anim = _animationQueue[i]
        if os.clock() - anim.queueTime >= anim.config.delay then
            AnimationController.StartAnimation(anim)
            table.remove(_animationQueue, i)
        end
    end
    
    -- 更新运行中的动画
    for _, anim in ipairs(_runningAnimations) do
        AnimationController.UpdateAnimation(anim, deltaTime)
    end
end

-- 更新单个动画
function AnimationController.UpdateAnimation(animation, deltaTime)
    local elapsed = os.clock() - animation.startTime
    animation.progress = elapsed / animation.config.duration
    
    -- 更新进度(用于进度回调)
    if animation.config.progressCallback then
        animation.config.progressCallback(animation.progress)
    end
    
    -- 检查是否超时
    if elapsed > animation.config.duration + 1.0 then  -- 容错1秒
        AnimationController.OnAnimationComplete(animation, false)
    end
end

-- 获取动画配置
function AnimationController.GetAnimationConfig(animName)
    -- 动画配置数据库
    local animationConfigs = {
        ["PageSlideIn"] = {
            duration = 0.3,
            easing = "easeOutCubic",
            type = "translate",
            allowMultiple = false,
            queuePolicy = "replace"
        },
        
        ["PageSlideOut"] = {
            duration = 0.3,
            easing = "easeInCubic",
            type = "translate",
            allowMultiple = false,
            queuePolicy = "replace"
        },
        
        ["ButtonPress"] = {
            duration = 0.15,
            easing = "easeOutBack",
            type = "scale",
            allowMultiple = true,
            queuePolicy = "queue"
        },
        
        ["NotificationShow"] = {
            duration = 0.25,
            easing = "easeOutElastic",
            type = "fade_and_slide",
            delay = 0.1,
            allowMultiple = true
        },
        
        ["NotificationHide"] = {
            duration = 0.2,
            easing = "easeInCubic",
            type = "fade"
        },
        
        ["HighlightPulse"] = {
            duration = 1.5,
            easing = "easeInOutSine",
            type = "pulse",
            loop = true,
            allowMultiple = false
        }
    }
    
    return animationConfigs[animName]
end

-- 创建复杂动画序列
function AnimationController.CreateAnimationSequence(name, animations)
    local sequence = {
        name = name,
        animations = animations,
        currentIndex = 1,
        state = "idle"
    }
    
    -- 序列播放方法
    function sequence:Play(callback)
        self.state = "playing"
        self.callback = callback
        self.currentIndex = 1
        
        AnimationController.PlayAnimation(
            self.animations[1].name,
            self.animations[1].params,
            function(success)
                if success then
                    sequence:PlayNext()
                else
                    sequence:Stop("animation_failed")
                end
            end
        )
    end
    
    -- 播放下一个动画
    function sequence:PlayNext()
        self.currentIndex = self.currentIndex + 1
        
        if self.currentIndex > #self.animations then
            -- 序列完成
            self.state = "completed"
            if self.callback then
                self.callback(true)
            end
            EventManager.Dispatch("AnimationSequenceComplete", {
                name = self.name
            })
        else
            -- 继续播放下一个
            local nextAnim = self.animations[self.currentIndex]
            AnimationController.PlayAnimation(
                nextAnim.name,
                nextAnim.params,
                function(success)
                    if success then
                        sequence:PlayNext()
                    else
                        sequence:Stop("animation_failed")
                    end
                end
            )
        end
    end
    
    -- 停止序列
    function sequence:Stop(reason)
        self.state = "stopped"
        
        -- 停止当前动画
        local currentAnim = self.animations[self.currentIndex]
        if currentAnim then
            AnimationController.StopAnimation(currentAnim.name, reason)
        end
        
        if self.callback then
            self.callback(false, reason)
        end
    end
    
    return sequence
end

-- 预定义动画序列
AnimationController.Sequences = {
    -- 页面切换动画
    PageTransition = {
        {name = "PageSlideOut", params = {direction = "left"}},
        {name = "PageSlideIn", params = {direction = "right"}}
    },
    
    -- 通知显示动画
    NotificationFlow = {
        {name = "NotificationShow", params = {position = "top"}},
        {name = "Delay", params = {duration = 3}},
        {name = "NotificationHide", params = {}}
    }
}

return AnimationController

4.2 Unity C#侧动画接口

// UIAnimationController.cs - Unity侧动画控制器
using UnityEngine;
using UnityEngine.UI;
using DG.Tweening;  // 使用DoTween动画插件
using System.Collections.Generic;

public class UIAnimationController : MonoBehaviour
{
    // 动画对象池
    private Dictionary<string, List<GameObject>> animationPool = 
        new Dictionary<string, List<GameObject>>();
    
    // 启动UI动画(由Lua调用)
    public void StartUIAnimation(string animName, LuaTable paramsTable, float duration)
    {
        // 将Lua表转换为C#字典
        Dictionary<string, object> parameters = 
            LuaTableToDictionary(paramsTable);
        
        // 根据动画名称执行不同的动画
        switch (animName)
        {
            case "PageSlideIn":
                PlayPageSlideIn(parameters, duration);
                break;
                
            case "PageSlideOut":
                PlayPageSlideOut(parameters, duration);
                break;
                
            case "ButtonPress":
                PlayButtonPress(parameters, duration);
                break;
                
            case "NotificationShow":
                PlayNotificationShow(parameters, duration);
                break;
                
            default:
                Debug.LogWarning($"未知的动画类型: {animName}");
                break;
        }
    }
    
    // 页面滑入动画
    private void PlayPageSlideIn(Dictionary<string, object> parameters, float duration)
    {
        string pageName = parameters.ContainsKey("page") ? 
            parameters["page"].ToString() : "CurrentPage";
        string direction = parameters.ContainsKey("direction") ? 
            parameters["direction"].ToString() : "right";
        
        GameObject page = GameObject.Find(pageName);
        if (page == null) return;
        
        RectTransform rt = page.GetComponent<RectTransform>();
        Vector2 startPos = GetStartPosition(direction, rt);
        Vector2 endPos = rt.anchoredPosition;
        
        // 设置初始位置
        rt.anchoredPosition = startPos;
        
        // 执行动画
        rt.DOAnchorPos(endPos, duration)
            .SetEase(Ease.OutCubic)
            .OnComplete(() => {
                // 动画完成,通知Lua
                XLuaManager.Instance.CallLuaFunction(
                    "AnimationController.OnAnimationComplete", 
                    animName, true);
            });
    }
    
    // 按钮按压动画
    private void PlayButtonPress(Dictionary<string, object> parameters, float duration)
    {
        string buttonName = parameters.ContainsKey("button") ? 
            parameters["button"].ToString() : "";
        
        GameObject buttonObj = GameObject.Find(buttonName);
        if (buttonObj == null) return;
        
        Transform buttonTransform = buttonObj.transform;
        Vector3 originalScale = buttonTransform.localScale;
        
        // 按压效果:先缩小再恢复
        buttonTransform.DOScale(originalScale * 0.9f, duration * 0.5f)
            .SetEase(Ease.OutBack)
            .OnComplete(() => {
                buttonTransform.DOScale(originalScale, duration * 0.5f)
                    .SetEase(Ease.OutElastic)
                    .OnComplete(() => {
                        // 动画完成,通知Lua
                        XLuaManager.Instance.CallLuaFunction(
                            "AnimationController.OnAnimationComplete", 
                            "ButtonPress", true);
                    });
            });
    }
    
    // 获取起始位置
    private Vector2 GetStartPosition(string direction, RectTransform rt)
    {
        Vector2 screenSize = new Vector2(Screen.width, Screen.height);
        
        switch (direction)
        {
            case "left":
                return new Vector2(-screenSize.x, rt.anchoredPosition.y);
            case "right":
                return new Vector2(screenSize.x, rt.anchoredPosition.y);
            case "top":
                return new Vector2(rt.anchoredPosition.x, screenSize.y);
            case "bottom":
                return new Vector2(rt.anchoredPosition.x, -screenSize.y);
            default:
                return new Vector2(screenSize.x, rt.anchoredPosition.y);
        }
    }
    
    // 将Lua表转换为字典
    private Dictionary<string, object> LuaTableToDictionary(LuaTable luaTable)
    {
        Dictionary<string, object> dict = new Dictionary<string, object>();
        
        if (luaTable != null)
        {
            foreach (var pair in luaTable.GetKeys<string>())
            {
                dict[pair] = luaTable.Get<string, object>(pair);
            }
        }
        
        return dict;
    }
    
    // 创建动画对象
    public GameObject CreateAnimationObject(string prefabName, Transform parent = null)
    {
        if (!animationPool.ContainsKey(prefabName))
        {
            animationPool[prefabName] = new List<GameObject>();
        }
        
        // 查找可重用的对象
        foreach (var obj in animationPool[prefabName])
        {
            if (!obj.activeInHierarchy)
            {
                obj.SetActive(true);
                return obj;
            }
        }
        
        // 创建新对象
        GameObject prefab = Resources.Load<GameObject>($"Animations/{prefabName}");
        if (prefab == null)
        {
            Debug.LogError($"动画预制体不存在: {prefabName}");
            return null;
        }
        
        GameObject newObj = Instantiate(prefab, parent);
        animationPool[prefabName].Add(newObj);
        
        return newObj;
    }
}

第五部分:Lua与底层车控系统通信对接

5.1 通信协议与数据解析

-- CanBusManager.lua - CAN总线通信管理
local CanBusManager = {}
local _canParser = nil
local _canWriters = {}
local _subscriptions = {}
local _messageQueue = {}
local _isConnected = false

-- 初始化CAN总线管理器
function CanBusManager.Init(config)
    print("[LUA] 初始化CAN总线管理器")
    
    -- 加载CAN解析配置文件
    CanBusManager.LoadCanDatabase(config.canDatabasePath)
    
    -- 注册到事件系统
    EventManager.AddListener("VehicleCommand", CanBusManager.OnVehicleCommand)
    
    -- 启动CAN通信
    CanBusManager.StartCanCommunication()
end

-- 加载CAN数据库(DBC文件解析结果)
function CanBusManager.LoadCanDatabase(dbPath)
    -- 这里是从C#加载已解析的DBC文件
    local canDb = CS.CanDatabaseLoader.Load(dbPath)
    
    _canParser = {
        messages = {},
        signals = {}
    }
    
    -- 解析消息定义
    for _, msg in ipairs(canDb.messages) do
        _canParser.messages[msg.id] = {
            name = msg.name,
            length = msg.length,
            signals = {}
        }
        
        -- 解析信号定义
        for _, sig in ipairs(msg.signals) do
            _canParser.signals[sig.name] = {
                messageId = msg.id,
                startBit = sig.startBit,
                length = sig.length,
                factor = sig.factor,
                offset = sig.offset,
                min = sig.min,
                max = sig.max,
                unit = sig.unit
            }
            
            _canParser.messages[msg.id].signals[sig.name] = 
                _canParser.signals[sig.name]
        end
    end
    
    print(string.format("加载了 %d 个CAN消息,%d 个信号", 
        #canDb.messages, #canDb.signals))
end

-- 启动CAN通信
function CanBusManager.StartCanCommunication()
    -- 通过C#连接到CAN总线
    local success = CS.CanBusInterface.Connect()
    
    if success then
        _isConnected = true
        print("CAN总线连接成功")
        
        -- 启动接收线程
        CanBusManager.StartReceiving()
        
        -- 发送心跳包
        CanBusManager.StartHeartbeat()
    else
        print("错误:CAN总线连接失败")
        EventManager.Dispatch("SystemError", {
            source = "CanBusManager",
            message = "CAN总线连接失败",
            severity = "high"
        })
    end
end

-- 启动数据接收
function CanBusManager.StartReceiving()
    -- 注册C#回调
    CS.CanBusInterface.SetReceiveCallback(function(canId, data)
        CanBusManager.OnCanMessageReceived(canId, data)
    end)
    
    print("CAN数据接收已启动")
end

-- CAN消息接收回调
function CanBusManager.OnCanMessageReceived(canId, data)
    -- 将消息加入队列
    table.insert(_messageQueue, {
        id = canId,
        data = data,
        timestamp = os.time(),
        receivedAt = os.clock()
    })
    
    -- 解析消息
    CanBusManager.ParseCanMessage(canId, data)
end

-- 解析CAN消息
function CanBusManager.ParseCanMessage(canId, data)
    local msgConfig = _canParser.messages[canId]
    if not msgConfig then
        -- 未知的消息ID
        return
    end
    
    local parsedData = {}
    
    -- 解析每个信号
    for signalName, sigConfig in pairs(msgConfig.signals) do
        local value = CanBusManager.ExtractSignal(data, sigConfig)
        parsedData[signalName] = value
        
        -- 通知订阅者
        CanBusManager.NotifySubscribers(signalName, value)
        
        -- 更新车辆数据管理器
        VehicleDataManager.Update(signalName, value)
    end
    
    -- 记录原始数据(用于诊断)
    EventManager.Dispatch("CanRawData", {
        id = canId,
        data = data,
        parsed = parsedData
    })
end

-- 提取信号值
function CanBusManager.ExtractSignal(data, sigConfig)
    -- 简化版信号提取
    -- 实际实现需要处理字节顺序、符号位等
    
    local rawValue = 0
    local byteIndex = math.floor(sigConfig.startBit / 8)
    local bitIndex = sigConfig.startBit % 8
    
    -- 提取原始值(简化处理)
    if byteIndex + 1 <= #data then
        rawValue = data:byte(byteIndex + 1)
    end
    
    -- 应用因子和偏移
    local physicalValue = rawValue * sigConfig.factor + sigConfig.offset
    
    -- 限制范围
    if sigConfig.min then
        physicalValue = math.max(sigConfig.min, physicalValue)
    end
    
    if sigConfig.max then
        physicalValue = math.min(sigConfig.max, physicalValue)
    end
    
    return physicalValue
end

-- 订阅CAN信号
function CanBusManager.Subscribe(signalName, callback)
    if not _subscriptions[signalName] then
        _subscriptions[signalName] = {}
    end
    
    table.insert(_subscriptions[signalName], callback)
    
    return function()
        -- 返回取消订阅的函数
        CanBusManager.Unsubscribe(signalName, callback)
    end
end

-- 取消订阅
function CanBusManager.Unsubscribe(signalName, callback)
    if not _subscriptions[signalName] then return end
    
    for i, cb in ipairs(_subscriptions[signalName]) do
        if cb == callback then
            table.remove(_subscriptions[signalName], i)
            break
        end
    end
end

-- 通知订阅者
function CanBusManager.NotifySubscribers(signalName, value)
    if not _subscriptions[signalName] then return end
    
    for _, callback in ipairs(_subscriptions[signalName]) do
        -- 使用协程避免阻塞
        coroutine.resume(coroutine.create(function()
            callback(value)
        end))
    end
end

-- 发送CAN消息
function CanBusManager.SendCanMessage(messageId, data)
    if not _isConnected then
        print("警告:CAN总线未连接,无法发送消息")
        return false
    end
    
    -- 通过C#接口发送
    return CS.CanBusInterface.Send(messageId, data)
end

-- 发送车辆命令
function CanBusManager.OnVehicleCommand(command)
    print("收到车辆命令:", command.action)
    
    -- 将命令转换为CAN消息
    local canMessage = CanBusManager.CommandToCanMessage(command)
    if canMessage then
        local success = CanBusManager.SendCanMessage(
            canMessage.id, 
            canMessage.data
        )
        
        if success then
            EventManager.Dispatch("CommandExecuted", {
                command = command,
                timestamp = os.time()
            })
        else
            EventManager.Dispatch("CommandFailed", {
                command = command,
                reason = "can_send_failed"
            })
        end
    end
end

-- 命令转CAN消息
function CanBusManager.CommandToCanMessage(command)
    -- 命令映射表
    local commandMapping = {
        ["SetClimateTemperature"] = {
            id = 0x123,
            builder = function(cmd)
                local temp = cmd.value * 10  -- 转换为0.1度精度
                return string.pack("I2", temp)
            end
        },
        
        ["SetSeatHeating"] = {
            id = 0x124,
            builder = function(cmd)
                local level = cmd.level or 0
                return string.char(level)
            end
        },
        
        ["LockDoors"] = {
            id = 0x125,
            builder = function(cmd)
                return string.char(0x01)
            end
        },
        
        ["UnlockDoors"] = {
            id = 0x125,
            builder = function(cmd)
                return string.char(0x00)
            end
        }
    }
    
    local mapping = commandMapping[command.action]
    if not mapping then
        print("警告:未知的车辆命令:", command.action)
        return nil
    end
    
    return {
        id = mapping.id,
        data = mapping.builder(command)
    }
end

-- 启动心跳包
function CanBusManager.StartHeartbeat()
    local heartbeatCoroutine = coroutine.create(function()
        while _isConnected do
            -- 发送心跳包
            CanBusManager.SendCanMessage(0x100, string.char(0xAA))
            
            -- 等待1秒
            coroutine.wait(1.0)
        end
    end)
    
    coroutine.resume(heartbeatCoroutine)
end

-- 获取连接状态
function CanBusManager.IsConnected()
    return _isConnected
end

-- 获取消息统计
function CanBusManager.GetStatistics()
    return {
        queueSize = #_messageQueue,
        isConnected = _isConnected,
        receivedCount = #_messageQueue,
        subscriptionCount = 0  -- 需要计算订阅总数
    }
end

return CanBusManager

5.2 C#侧CAN总线接口

// CanBusInterface.cs - Unity与底层CAN总线的接口
using UnityEngine;
using System;
using System.Runtime.InteropServices;
using System.Threading;

public class CanBusInterface : MonoBehaviour
{
    // CAN接口类型枚举
    public enum CanInterfaceType
    {
        SocketCAN,      // Linux
        Vector,         // Windows
        PeakCAN,        // PCAN
        AndroidJ2534,   // Android
        Simulated       // 模拟模式
    }
    
    // 委托定义
    public delegate void CanMessageReceivedDelegate(uint canId, byte[] data);
    
    // 事件
    public static event CanMessageReceivedDelegate OnCanMessageReceived;
    
    // 当前接口类型
    private static CanInterfaceType currentInterface = CanInterfaceType.Simulated;
    
    // 连接状态
    private static bool isConnected = false;
    
    // 接收线程
    private static Thread receiveThread;
    private static bool keepReceiving = false;
    
    // 初始化CAN接口
    public static bool Connect(CanInterfaceType interfaceType = CanInterfaceType.Simulated)
    {
        if (isConnected)
        {
            Debug.LogWarning("CAN接口已经连接");
            return true;
        }
        
        currentInterface = interfaceType;
        
        bool result = false;
        
        switch (interfaceType)
        {
            case CanInterfaceType.SocketCAN:
                result = ConnectSocketCAN();
                break;
                
            case CanInterfaceType.AndroidJ2534:
                result = ConnectAndroidJ2534();
                break;
                
            case CanInterfaceType.Simulated:
                result = ConnectSimulated();
                break;
                
            default:
                Debug.LogError($"不支持的CAN接口类型: {interfaceType}");
                return false;
        }
        
        if (result)
        {
            isConnected = true;
            StartReceivingThread();
            Debug.Log($"CAN接口连接成功: {interfaceType}");
        }
        
        return result;
    }
    
    // 连接SocketCAN(Linux车机)
    private static bool ConnectSocketCAN()
    {
#if UNITY_STANDALONE_LINUX || UNITY_ANDROID
        try
        {
            // 调用Linux原生库
            int socket = SocketCAN_Connect("can0", 500000); // 500kbps
            return socket > 0;
        }
        catch (Exception e)
        {
            Debug.LogError($"SocketCAN连接失败: {e.Message}");
            return false;
        }
#else
        Debug.LogWarning("SocketCAN仅支持Linux/Android平台");
        return false;
#endif
    }
    
    // 连接Android J2534
    private static bool ConnectAndroidJ2534()
    {
#if UNITY_ANDROID
        try
        {
            using (AndroidJavaClass j2534 = new AndroidJavaClass("com.vehicle.can.J2534Interface"))
            {
                return j2534.CallStatic<bool>("connect");
            }
        }
        catch (Exception e)
        {
            Debug.LogError($"J2534连接失败: {e.Message}");
            return false;
        }
#else
        Debug.LogWarning("J2534仅支持Android平台");
        return false;
#endif
    }
    
    // 连接模拟模式
    private static bool ConnectSimulated()
    {
        // 模拟模式总是成功
        Debug.Log("使用模拟CAN接口");
        return true;
    }
    
    // 启动接收线程
    private static void StartReceivingThread()
    {
        keepReceiving = true;
        receiveThread = new Thread(ReceiveThreadFunction);
        receiveThread.IsBackground = true;
        receiveThread.Start();
    }
    
    // 接收线程函数
    private static void ReceiveThreadFunction()
    {
        while (keepReceiving && isConnected)
        {
            switch (currentInterface)
            {
                case CanInterfaceType.Simulated:
                    ReceiveSimulatedData();
                    break;
                    
                case CanInterfaceType.SocketCAN:
                    ReceiveSocketCANData();
                    break;
            }
            
            Thread.Sleep(10); // 控制接收频率
        }
    }
    
    // 接收模拟数据
    private static void ReceiveSimulatedData()
    {
        // 模拟车辆数据
        SimulateVehicleData();
    }
    
    // 模拟车辆数据
    private static void SimulateVehicleData()
    {
        System.Random rand = new System.Random();
        float time = Time.time;
        
        // 模拟车速信号 (0x100)
        int speed = 60 + (int)(Mathf.Sin(time) * 20);
        byte[] speedData = BitConverter.GetBytes((ushort)speed);
        OnCanMessageReceived?.Invoke(0x100, speedData);
        
        // 模拟电池电量 (0x101)
        int battery = 80 + (int)(Mathf.Sin(time * 0.5f) * 10);
        byte[] batteryData = BitConverter.GetBytes((byte)battery);
        OnCanMessageReceived?.Invoke(0x101, batteryData);
        
        // 模拟车门状态 (0x102)
        int doorStatus = rand.Next(0, 2);
        byte[] doorData = BitConverter.GetBytes((byte)doorStatus);
        OnCanMessageReceived?.Invoke(0x102, doorData);
    }
    
    // 发送CAN消息
    public static bool Send(uint canId, byte[] data)
    {
        if (!isConnected)
        {
            Debug.LogWarning("CAN接口未连接,无法发送");
            return false;
        }
        
        if (data == null || data.Length > 8)
        {
            Debug.LogError("CAN数据无效(空或超过8字节)");
            return false;
        }
        
        switch (currentInterface)
        {
            case CanInterfaceType.Simulated:
                Debug.Log($"发送模拟CAN消息: ID=0x{canId:X}, Data={BitConverter.ToString(data)}");
                return true;
                
            case CanInterfaceType.SocketCAN:
                return SendSocketCAN(canId, data);
                
            default:
                Debug.LogError($"不支持的发送接口: {currentInterface}");
                return false;
        }
    }
    
    // 设置接收回调(供Lua调用)
    public static void SetReceiveCallback(LuaFunction callback)
    {
        OnCanMessageReceived = null; // 清除旧的回调
        
        if (callback != null)
        {
            OnCanMessageReceived = (canId, data) =>
            {
                // 在主线程调用Lua回调
                MainThreadDispatcher.ExecuteOnMainThread(() =>
                {
                    try
                    {
                        callback.Call(canId, data);
                    }
                    catch (Exception e)
                    {
                        Debug.LogError($"Lua回调执行失败: {e.Message}");
                    }
                });
            };
        }
    }
    
    // 断开连接
    public static void Disconnect()
    {
        keepReceiving = false;
        isConnected = false;
        
        if (receiveThread != null && receiveThread.IsAlive)
        {
            receiveThread.Join(1000);
        }
        
        Debug.Log("CAN接口已断开");
    }
    
    // 原生插件接口声明
#if UNITY_STANDALONE_LINUX || UNITY_ANDROID
    [DllImport("libsocketcan")]
    private static extern int SocketCAN_Connect(string interfaceName, int baudrate);
    
    [DllImport("libsocketcan")]
    private static extern bool SocketCAN_Send(int socket, uint canId, byte[] data, int length);
    
    [DllImport("libsocketcan")]
    private static extern bool SocketCAN_Receive(int socket, out uint canId, byte[] buffer, out int length);
#endif
    
    private static bool SendSocketCAN(uint canId, byte[] data)
    {
        // 实际实现会调用原生库
        return false;
    }
    
    private static void ReceiveSocketCANData()
    {
        // 实际实现会调用原生库接收数据
    }
}

第六部分:热更新机制详解

6.1 Lua热更新系统设计

-- HotfixManager.lua - 热更新管理器
local HotfixManager = {}
local _hotfixVersions = {}
local _patchCache = {}
local _isUpdating = false
local _updateListeners = {}

-- 初始化热更新管理器
function HotfixManager.Init()
    print("[LUA] 初始化热更新管理器")
    
    -- 加载版本信息
    HotfixManager.LoadVersionInfo()
    
    -- 注册事件监听
    EventManager.AddListener("CheckForUpdates", HotfixManager.CheckForUpdates)
    EventManager.AddListener("ApplyHotfix", HotfixManager.ApplyHotfix)
    
    -- 自动检查更新
    HotfixManager.AutoCheck()
end

-- 加载版本信息
function HotfixManager.LoadVersionInfo()
    local versionPath = HotfixManager.GetHotfixPath() .. "/version.json"
    
    if HotfixManager.FileExists(versionPath) then
        local content = HotfixManager.ReadFile(versionPath)
        _hotfixVersions = json.decode(content)
    else
        _hotfixVersions = {
            appVersion = "1.0.0",
            luaVersion = "1.0.0",
            patches = {}
        }
    end
end

-- 自动检查更新
function HotfixManager.AutoCheck()
    -- 创建定期检查的协程
    local checkCoroutine = coroutine.create(function()
        while true do
            -- 每天检查一次更新
            HotfixManager.CheckForUpdates()
            coroutine.wait(24 * 3600)  -- 24小时
        end
    end)
    
    coroutine.resume(checkCoroutine)
end

-- 检查更新
function HotfixManager.CheckForUpdates()
    if _isUpdating then
        print("正在更新中,跳过检查")
        return
    end
    
    print("检查热更新...")
    
    -- 通知开始检查
    EventManager.Dispatch("UpdateCheckStarted", {})
    
    -- 从服务器获取更新信息
    HotfixManager.FetchUpdateInfo(function(updateInfo, error)
        if error then
            print("检查更新失败:", error)
            EventManager.Dispatch("UpdateCheckFailed", {error = error})
            return
        end
        
        if not updateInfo then
            print("无可用更新")
            EventManager.Dispatch("NoUpdatesAvailable", {})
            return
        end
        
        -- 检查是否需要更新
        if HotfixManager.NeedsUpdate(updateInfo) then
            print("发现新版本:", updateInfo.luaVersion)
            
            -- 显示更新提示
            EventManager.Dispatch("UpdateAvailable", {
                version = updateInfo.luaVersion,
                size = updateInfo.totalSize,
                description = updateInfo.description
            })
        else
            print("已是最新版本")
            EventManager.Dispatch("AlreadyUpToDate", {})
        end
    end)
end

-- 从服务器获取更新信息
function HotfixManager.FetchUpdateInfo(callback)
    -- 构建请求URL
    local url = HotfixManager.GetUpdateServerUrl() .. "/check"
    local params = {
        appVersion = _hotfixVersions.appVersion,
        luaVersion = _hotfixVersions.luaVersion,
        deviceId = HotfixManager.GetDeviceId()
    }
    
    -- 使用C#的HTTP客户端
    CS.NetworkManager.Instance.HttpGet(url, params, function(response, error)
        if error then
            callback(nil, error)
            return
        end
        
        local updateInfo = json.decode(response)
        callback(updateInfo, nil)
    end)
end

-- 判断是否需要更新
function HotfixManager.NeedsUpdate(updateInfo)
    if not updateInfo or not updateInfo.luaVersion then
        return false
    end
    
    -- 简单的版本号比较
    local current = _hotfixVersions.luaVersion
    local available = updateInfo.luaVersion
    
    return HotfixManager.CompareVersions(available, current) > 0
end

-- 比较版本号
function HotfixManager.CompareVersions(v1, v2)
    local parts1 = {}
    for part in string.gmatch(v1, "%d+") do
        table.insert(parts1, tonumber(part))
    end
    
    local parts2 = {}
    for part in string.gmatch(v2, "%d+") do
        table.insert(parts2, tonumber(part))
    end
    
    for i = 1, math.max(#parts1, #parts2) do
        local p1 = parts1[i] or 0
        local p2 = parts2[i] or 0
        
        if p1 > p2 then return 1 end
        if p1 < p2 then return -1 end
    end
    
    return 0
end

-- 应用热更新
function HotfixManager.ApplyHotfix(patchInfo)
    if _isUpdating then
        print("正在更新中,请稍候")
        return false
    end
    
    _isUpdating = true
    
    print("开始应用热更新:", patchInfo.version)
    
    -- 通知开始更新
    EventManager.Dispatch("UpdateStarted", {
        version = patchInfo.version,
        totalSize = patchInfo.totalSize
    })
    
    -- 下载补丁文件
    HotfixManager.DownloadPatch(patchInfo, function(success, error)
        if not success then
            print("下载补丁失败:", error)
            EventManager.Dispatch("UpdateFailed", {error = error})
            _isUpdating = false
            return
        end
        
        -- 验证补丁
        if not HotfixManager.VerifyPatch(patchInfo) then
            print("补丁验证失败")
            EventManager.Dispatch("UpdateFailed", {error = "patch_verification_failed"})
            _isUpdating = false
            return
        end
        
        -- 应用补丁
        HotfixManager.ApplyPatchFiles(patchInfo)
        
        -- 更新版本信息
        HotfixManager.UpdateVersionInfo(patchInfo.version)
        
        -- 重新加载Lua模块
        HotfixManager.ReloadLuaModules()
        
        _isUpdating = false
        
        -- 通知更新完成
        EventManager.Dispatch("UpdateCompleted", {
            version = patchInfo.version
        })
        
        print("热更新应用完成")
    end)
    
    return true
end

-- 下载补丁
function HotfixManager.DownloadPatch(patchInfo, callback)
    local totalFiles = #patchInfo.files
    local downloaded = 0
    local errors = {}
    
    -- 创建下载目录
    local patchDir = HotfixManager.GetHotfixPath() .. "/" .. patchInfo.version
    HotfixManager.CreateDirectory(patchDir)
    
    -- 下载每个文件
    for i, fileInfo in ipairs(patchInfo.files) do
        local url = patchInfo.baseUrl .. "/" .. fileInfo.name
        local localPath = patchDir .. "/" .. fileInfo.name
        
        print("下载文件:", url, "->", localPath)
        
        -- 使用C#下载文件
        CS.NetworkManager.Instance.DownloadFile(url, localPath, function(success, error)
            if success then
                downloaded = downloaded + 1
                
                -- 更新进度
                local progress = downloaded / totalFiles
                EventManager.Dispatch("DownloadProgress", {
                    file = fileInfo.name,
                    progress = progress,
                    downloaded = downloaded,
                    total = totalFiles
                })
                
                -- 检查是否全部完成
                if downloaded == totalFiles then
                    callback(true, nil)
                end
            else
                table.insert(errors, fileInfo.name .. ": " .. error)
                
                -- 如果错误太多,放弃
                if #errors > 3 then
                    callback(false, table.concat(errors, "; "))
                end
            end
        end)
    end
end

-- 验证补丁
function HotfixManager.VerifyPatch(patchInfo)
    local patchDir = HotfixManager.GetHotfixPath() .. "/" .. patchInfo.version
    
    for _, fileInfo in ipairs(patchInfo.files) do
        local filePath = patchDir .. "/" .. fileInfo.name
        
        -- 检查文件是否存在
        if not HotfixManager.FileExists(filePath) then
            print("文件不存在:", filePath)
            return false
        end
        
        -- 检查文件大小
        local fileSize = HotfixManager.GetFileSize(filePath)
        if fileSize ~= fileInfo.size then
            print("文件大小不匹配:", filePath, fileSize, "!=", fileInfo.size)
            return false
        end
        
        -- 检查MD5(可选)
        if fileInfo.md5 then
            local md5 = HotfixManager.CalculateMD5(filePath)
            if md5 ~= fileInfo.md5 then
                print("MD5不匹配:", filePath)
                return false
            end
        end
    end
    
    return true
end

-- 应用补丁文件
function HotfixManager.ApplyPatchFiles(patchInfo)
    local patchDir = HotfixManager.GetHotfixPath() .. "/" .. patchInfo.version
    local targetDir = HotfixManager.GetHotfixPath() .. "/current"
    
    -- 创建目标目录
    HotfixManager.CreateDirectory(targetDir)
    
    -- 复制文件
    for _, fileInfo in ipairs(patchInfo.files) do
        local sourcePath = patchDir .. "/" .. fileInfo.name
        local targetPath = targetDir .. "/" .. fileInfo.name
        
        -- 确保目标目录存在
        local dir = HotfixManager.GetDirectory(targetPath)
        HotfixManager.CreateDirectory(dir)
        
        -- 复制文件
        HotfixManager.CopyFile(sourcePath, targetPath)
        
        print("应用文件:", fileInfo.name)
    end
    
    -- 更新补丁缓存
    _patchCache[patchInfo.version] = {
        appliedAt = os.time(),
        files = patchInfo.files
    }
end

-- 重新加载Lua模块
function HotfixManager.ReloadLuaModules()
    print("重新加载Lua模块...")
    
    -- 通知模块即将重新加载
    EventManager.Dispatch("BeforeLuaReload", {})
    
    -- 获取需要重新加载的模块列表
    local modulesToReload = HotfixManager.GetAffectedModules()
    
    -- 重新加载每个模块
    for _, moduleName in ipairs(modulesToReload) do
        print("重新加载模块:", moduleName)
        
        -- 从热更新目录重新加载
        local success = XLuaManager.Instance.ReloadModule(moduleName)
        
        if success then
            print("模块重新加载成功:", moduleName)
        else
            print("模块重新加载失败:", moduleName)
        end
    end
    
    -- 通知模块重新加载完成
    EventManager.Dispatch("AfterLuaReload", {
        modules = modulesToReload
    })
end

-- 获取受影响的模块
function HotfixManager.GetAffectedModules()
    -- 从补丁信息中分析需要重新加载的模块
    local modules = {}
    
    for version, patchInfo in pairs(_patchCache) do
        for _, fileInfo in ipairs(patchInfo.files) do
            -- 从文件名提取模块名
            local moduleName = HotfixManager.ExtractModuleName(fileInfo.name)
            if moduleName and not table.contains(modules, moduleName) then
                table.insert(modules, moduleName)
            end
        end
    end
    
    return modules
end

-- 从文件名提取模块名
function HotfixManager.ExtractModuleName(filename)
    -- 移除.lua扩展名,将路径分隔符替换为.
    local name = filename:gsub("%.lua$", "")
    name = name:gsub("/", ".")
    return name
end

-- 获取热更新路径
function HotfixManager.GetHotfixPath()
    return CS.UnityEngine.Application.persistentDataPath .. "/LuaHotfix"
end

-- 获取更新服务器URL
function HotfixManager.GetUpdateServerUrl()
    -- 可从配置读取
    return "https://update.example.com/api"
end

-- 获取设备ID
function HotfixManager.GetDeviceId()
    return CS.SystemInfo.deviceUniqueIdentifier
end

-- 添加更新监听器
function HotfixManager.AddUpdateListener(listener)
    table.insert(_updateListeners, listener)
end

-- 移除更新监听器
function HotfixManager.RemoveUpdateListener(listener)
    for i, l in ipairs(_updateListeners) do
        if l == listener then
            table.remove(_updateListeners, i)
            break
        end
    end
end

-- 回滚到上一个版本
function HotfixManager.Rollback()
    print("执行回滚操作...")
    
    -- 实现版本回滚逻辑
    -- ...
end

return HotfixManager

6.2 C#侧热更新支持

// LuaHotfixLoader.cs - C#侧热更新加载器
using UnityEngine;
using System.IO;
using System.Collections.Generic;

public class LuaHotfixLoader : MonoBehaviour
{
    // 热更新目录
    private string hotfixDirectory;
    
    // 已加载的热更新文件
    private Dictionary<string, TextAsset> hotfixFiles = 
        new Dictionary<string, TextAsset>();
    
    void Start()
    {
        // 初始化热更新目录
        hotfixDirectory = Path.Combine(
            Application.persistentDataPath, 
            "LuaHotfix"
        );
        
        if (!Directory.Exists(hotfixDirectory))
        {
            Directory.CreateDirectory(hotfixDirectory);
        }
        
        // 加载热更新文件
        LoadHotfixFiles();
    }
    
    // 加载热更新文件
    private void LoadHotfixFiles()
    {
        // 检查热更新目录
        string currentDir = Path.Combine(hotfixDirectory, "current");
        
        if (!Directory.Exists(currentDir))
        {
            Debug.Log("热更新目录不存在,使用内置Lua脚本");
            return;
        }
        
        // 遍历热更新目录
        string[] luaFiles = Directory.GetFiles(currentDir, "*.lua", 
            SearchOption.AllDirectories);
        
        foreach (string filePath in luaFiles)
        {
            // 读取文件内容
            string content = File.ReadAllText(filePath);
            
            // 转换为相对路径(作为模块名)
            string relativePath = GetRelativePath(filePath, currentDir)
                .Replace(".lua", "")
                .Replace("\\", "/");
            
            // 创建TextAsset(模拟)
            TextAsset luaAsset = ScriptableObject.CreateInstance<TextAsset>();
            luaAsset.name = relativePath;
            
            // 使用反射设置text属性
            typeof(TextAsset).GetProperty("text")?
                .SetValue(luaAsset, content, null);
            
            // 添加到缓存
            hotfixFiles[relativePath] = luaAsset;
            
            Debug.Log($"加载热更新Lua文件: {relativePath}");
        }
    }
    
    // 自定义Lua加载器(供XLua使用)
    public byte[] HotfixLoader(ref string filepath)
    {
        // 检查热更新缓存
        if (hotfixFiles.ContainsKey(filepath))
        {
            string content = hotfixFiles[filepath].text;
            return System.Text.Encoding.UTF8.GetBytes(content);
        }
        
        // 回退到内置资源
        return null;
    }
    
    // 获取相对路径
    private string GetRelativePath(string fullPath, string basePath)
    {
        if (!fullPath.StartsWith(basePath))
        {
            return fullPath;
        }
        
        return fullPath.Substring(basePath.Length + 1);
    }
    
    // 检查热更新
    public void CheckForUpdates()
    {
        StartCoroutine(CheckForUpdatesCoroutine());
    }
    
    private System.Collections.IEnumerator CheckForUpdatesCoroutine()
    {
        // 从服务器检查更新
        string versionUrl = "http://your-server.com/api/version";
        
        using (var request = UnityEngine.Networking.UnityWebRequest.Get(versionUrl))
        {
            yield return request.SendWebRequest();
            
            if (request.result == UnityEngine.Networking.UnityWebRequest.Result.Success)
            {
                // 解析版本信息
                string json = request.downloadHandler.text;
                // ... 处理版本信息
            }
        }
    }
    
    // 下载热更新文件
    public void DownloadHotfix(string version, System.Action<bool> callback)
    {
        StartCoroutine(DownloadHotfixCoroutine(version, callback));
    }
    
    private System.Collections.IEnumerator DownloadHotfixCoroutine(
        string version, System.Action<bool> callback)
    {
        // 创建版本目录
        string versionDir = Path.Combine(hotfixDirectory, version);
        if (!Directory.Exists(versionDir))
        {
            Directory.CreateDirectory(versionDir);
        }
        
        // 下载补丁清单
        string manifestUrl = $"http://your-server.com/patches/{version}/manifest.json";
        
        using (var request = UnityEngine.Networking.UnityWebRequest.Get(manifestUrl))
        {
            yield return request.SendWebRequest();
            
            if (request.result != UnityEngine.Networking.UnityWebRequest.Result.Success)
            {
                Debug.LogError($"下载清单失败: {request.error}");
                callback?.Invoke(false);
                yield break;
            }
            
            // 解析清单
            PatchManifest manifest = JsonUtility.FromJson<PatchManifest>(
                request.downloadHandler.text);
            
            // 下载每个文件
            foreach (var file in manifest.files)
            {
                string fileUrl = $"http://your-server.com/patches/{version}/{file.name}";
                string localPath = Path.Combine(versionDir, file.name);
                
                // 确保目录存在
                Directory.CreateDirectory(Path.GetDirectoryName(localPath));
                
                using (var fileRequest = new UnityEngine.Networking.UnityWebRequest(
                    fileUrl, UnityEngine.Networking.UnityWebRequest.kHttpVerbGET))
                {
                    fileRequest.downloadHandler = 
                        new UnityEngine.Networking.DownloadHandlerFile(localPath);
                    
                    yield return fileRequest.SendWebRequest();
                    
                    if (fileRequest.result != 
                        UnityEngine.Networking.UnityWebRequest.Result.Success)
                    {
                        Debug.LogError($"下载文件失败: {file.name}, {fileRequest.error}");
                        callback?.Invoke(false);
                        yield break;
                    }
                    
                    Debug.Log($"下载完成: {file.name}");
                }
            }
            
            // 应用热更新
            ApplyHotfix(version);
            callback?.Invoke(true);
        }
    }
    
    // 应用热更新
    private void ApplyHotfix(string version)
    {
        string versionDir = Path.Combine(hotfixDirectory, version);
        string currentDir = Path.Combine(hotfixDirectory, "current");
        
        // 备份当前版本
        if (Directory.Exists(currentDir))
        {
            string backupDir = Path.Combine(hotfixDirectory, 
                $"backup_{System.DateTime.Now:yyyyMMddHHmmss}");
            Directory.Move(currentDir, backupDir);
        }
        
        // 复制新版本到current
        CopyDirectory(versionDir, currentDir);
        
        // 重新加载Lua文件
        LoadHotfixFiles();
        
        // 通知Lua重新加载模块
        XLuaManager.Instance.CallLuaFunction("HotfixManager.OnHotfixApplied", version);
    }
    
    // 复制目录
    private void CopyDirectory(string sourceDir, string targetDir)
    {
        Directory.CreateDirectory(targetDir);
        
        foreach (string file in Directory.GetFiles(sourceDir))
        {
            string destFile = Path.Combine(targetDir, Path.GetFileName(file));
            File.Copy(file, destFile, true);
        }
        
        foreach (string subDir in Directory.GetDirectories(sourceDir))
        {
            string destDir = Path.Combine(targetDir, Path.GetFileName(subDir));
            CopyDirectory(subDir, destDir);
        }
    }
}

// 补丁清单类
[System.Serializable]
public class PatchManifest
{
    public string version;
    public PatchFile[] files;
}

[System.Serializable]
public class PatchFile
{
    public string name;
    public int size;
    public string md5;
}

第七部分:性能优化与最佳实践

7.1 Lua性能优化策略

-- PerformanceOptimizer.lua - 性能优化工具
local PerformanceOptimizer = {}
local _profileData = {}
local _enabled = false

-- 初始化性能分析
function PerformanceOptimizer.Init()
    print("[LUA] 初始化性能优化器")
    
    -- 注册性能监控事件
    EventManager.AddListener("StartProfiling", PerformanceOptimizer.StartProfiling)
    EventManager.AddListener("StopProfiling", PerformanceOptimizer.StopProfiling)
    
    -- 自动性能监控
    PerformanceOptimizer.SetupAutoProfiling()
end

-- 启动性能分析
function PerformanceOptimizer.StartProfiling()
    _enabled = true
    _profileData = {}
    
    print("性能分析已启动")
end

-- 停止性能分析
function PerformanceOptimizer.StopProfiling()
    _enabled = false
    
    -- 生成报告
    PerformanceOptimizer.GenerateReport()
    
    print("性能分析已停止")
end

-- 自动性能分析设置
function PerformanceOptimizer.SetupAutoProfiling()
    -- 定期采样性能数据
    local sampleCoroutine = coroutine.create(function()
        while true do
            if _enabled then
                PerformanceOptimizer.SamplePerformance()
            end
            coroutine.wait(5.0)  -- 每5秒采样一次
        end
    end)
    
    coroutine.resume(sampleCoroutine)
end

-- 采样性能数据
function PerformanceOptimizer.SamplePerformance()
    local sample = {
        timestamp = os.time(),
        memory = collectgarbage("count"),  -- Lua内存使用(KB)
        frameTime = 0,  -- 需要从C#获取
        updateCount = 0,
        gcCount = 0
    }
    
    table.insert(_profileData, sample)
    
    -- 限制数据量
    if #_profileData > 1000 then
        table.remove(_profileData, 1)
    end
    
    -- 检查内存泄漏
    PerformanceOptimizer.CheckMemoryLeak()
end

-- 检查内存泄漏
function PerformanceOptimizer.CheckMemoryLeak()
    if #_profileData < 10 then return end
    
    local recentMemory = 0
    for i = #_profileData - 9, #_profileData do
        recentMemory = recentMemory + _profileData[i].memory
    end
    recentMemory = recentMemory / 10
    
    local oldMemory = 0
    for i = 1, 10 do
        oldMemory = oldMemory + _profileData[i].memory
    end
    oldMemory = oldMemory / 10
    
    -- 如果内存增长超过阈值,发出警告
    if recentMemory > oldMemory * 1.5 then
        print(string.format("警告:可能的内存泄漏,内存从 %.1fK 增长到 %.1fK", 
            oldMemory, recentMemory))
        
        EventManager.Dispatch("PerformanceWarning", {
            type = "memory_leak",
            oldValue = oldMemory,
            newValue = recentMemory
        })
    end
end

-- 生成性能报告
function PerformanceOptimizer.GenerateReport()
    if #_profileData == 0 then
        print("无性能数据")
        return
    end
    
    local totalMemory = 0
    local maxMemory = 0
    local minMemory = math.huge
    
    for _, sample in ipairs(_profileData) do
        totalMemory = totalMemory + sample.memory
        maxMemory = math.max(maxMemory, sample.memory)
        minMemory = math.min(minMemory, sample.memory)
    end
    
    local avgMemory = totalMemory / #_profileData
    
    print("===== 性能报告 =====")
    print(string.format("采样数: %d", #_profileData))
    print(string.format("平均内存: %.1f KB", avgMemory))
    print(string.format("最大内存: %.1f KB", maxMemory))
    print(string.format("最小内存: %.1f KB", minMemory))
    print("=====================")
    
    -- 将报告发送到C#侧
    CS.PerformanceMonitor.Instance.RecordLuaPerformance({
        avgMemory = avgMemory,
        maxMemory = maxMemory,
        minMemory = minMemory,
        sampleCount = #_profileData
    })
end

-- Lua对象池
local ObjectPool = {}
ObjectPool.__index = ObjectPool

function ObjectPool.New(objectType, createFunc, resetFunc)
    local pool = {
        objects = {},
        createFunc = createFunc,
        resetFunc = resetFunc,
        type = objectType,
        totalCreated = 0,
        totalReused = 0
    }
    
    return setmetatable(pool, ObjectPool)
end

function ObjectPool:Get()
    if #self.objects > 0 then
        local obj = table.remove(self.objects)
        if self.resetFunc then
            self.resetFunc(obj)
        end
        self.totalReused = self.totalReused + 1
        return obj
    else
        self.totalCreated = self.totalCreated + 1
        return self.createFunc()
    end
end

function ObjectPool:Return(obj)
    if self.resetFunc then
        self.resetFunc(obj)
    end
    table.insert(self.objects, obj)
end

function ObjectPool:GetStats()
    return {
        type = self.type,
        available = #self.objects,
        totalCreated = self.totalCreated,
        totalReused = self.totalReused,
        reuseRate = self.totalReused / math.max(self.totalCreated, 1)
    }
end

-- 全局对象池管理器
PerformanceOptimizer.ObjectPools = {}

function PerformanceOptimizer.GetOrCreatePool(name, createFunc, resetFunc)
    if not PerformanceOptimizer.ObjectPools[name] then
        PerformanceOptimizer.ObjectPools[name] = 
            ObjectPool.New(name, createFunc, resetFunc)
    end
    
    return PerformanceOptimizer.ObjectPools[name]
end

-- 高频调用的优化
function PerformanceOptimizer.OptimizeHighFrequencyCalls()
    -- 1. 缓存频繁访问的全局变量
    local math_floor = math.floor
    local string_format = string.format
    local table_insert = table.insert
    local table_remove = table.remove
    
    -- 2. 避免在循环中创建表
    -- 不好的写法:
    -- for i = 1, 1000 do
    --     local data = {x = i, y = i*2}  -- 每次循环创建新表
    -- end
    
    -- 好的写法:
    local data = {}
    for i = 1, 1000 do
        data.x = i
        data.y = i * 2
        -- 使用data
        data.x = nil
        data.y = nil
    end
    
    -- 3. 使用局部变量
    local vehicleData = VehicleDataManager.GetCurrentData()
    local speed = vehicleData.speed
    local battery = vehicleData.batteryLevel
    
    -- 4. 避免不必要的字符串连接
    -- 不好的写法:
    -- local fullName = firstName .. " " .. lastName .. " " .. middleName
    
    -- 好的写法:
    local fullName = string_format("%s %s %s", 
        firstName, lastName, middleName)
    
    -- 5. 优化表访问
    local config = UIController.GetConfig()
    local theme = config.theme  -- 一次访问
    local fontSize = config.fontSize
    
    -- 而不是多次访问
    -- local theme = UIController.GetConfig().theme
    -- local fontSize = UIController.GetConfig().fontSize
end

-- 内存使用监控
function PerformanceOptimizer.MonitorMemoryUsage()
    local memoryStats = {
        luaMemory = collectgarbage("count"),
        timestamp = os.time()
    }
    
    -- 统计表数量(近似)
    local tableCount = 0
    local function countTables(obj, visited)
        visited = visited or {}
        
        if type(obj) == "table" then
            if visited[obj] then return 0 end
            visited[obj] = true
            
            tableCount = tableCount + 1
            for k, v in pairs(obj) do
                countTables(k, visited)
                countTables(v, visited)
            end
        end
    end
    
    -- 从全局环境开始计数
    countTables(_G)
    memoryStats.tableCount = tableCount
    
    return memoryStats
end

-- 强制垃圾回收(谨慎使用)
function PerformanceOptimizer.ForceGC()
    local before = collectgarbage("count")
    collectgarbage("collect")
    local after = collectgarbage("count")
    
    print(string.format("垃圾回收: %.1f KB -> %.1f KB (回收 %.1f KB)", 
        before, after, before - after))
    
    return before - after
end

return PerformanceOptimizer

7.2 C#侧性能监控

// PerformanceMonitor.cs - Unity侧性能监控
using UnityEngine;
using System.Collections.Generic;
using System.Diagnostics;

public class PerformanceMonitor : MonoBehaviour
{
    // 性能数据
    private struct PerformanceData
    {
        public float frameTime;
        public float fps;
        public float memoryUsage;
        public float luaMemory;
        public long updateCount;
        public long luaGCCount;
    }
    
    // 监控设置
    [Header("监控设置")]
    public bool enableMonitoring = true;
    public float updateInterval = 1.0f;
    public int maxSamples = 1000;
    
    // 性能数据列表
    private List<PerformanceData> performanceHistory = 
        new List<PerformanceData>();
    
    // 计时器
    private float updateTimer = 0;
    private int frameCount = 0;
    private float frameTimeTotal = 0;
    
    // Lua性能回调
    private LuaFunction luaMemoryCallback;
    
    void Start()
    {
        if (enableMonitoring)
        {
            StartMonitoring();
        }
    }
    
    void Update()
    {
        if (!enableMonitoring) return;
        
        // 累计帧时间
        frameTimeTotal += Time.unscaledDeltaTime;
        frameCount++;
        
        updateTimer += Time.unscaledDeltaTime;
        
        // 定时记录性能数据
        if (updateTimer >= updateInterval)
        {
            RecordPerformanceData();
            updateTimer = 0;
        }
        
        // 检查性能阈值
        CheckPerformanceThresholds();
    }
    
    // 记录性能数据
    private void RecordPerformanceData()
    {
        PerformanceData data = new PerformanceData();
        
        // 计算FPS和帧时间
        data.fps = frameCount / updateTimer;
        data.frameTime = (frameTimeTotal / frameCount) * 1000; // 转换为毫秒
        
        // 获取内存使用
        data.memoryUsage = UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong() 
            / (1024f * 1024f); // 转换为MB
        
        // 获取Lua内存(通过回调)
        if (luaMemoryCallback != null)
        {
            var result = luaMemoryCallback.Call();
            if (result != null && result.Length > 0)
            {
                data.luaMemory = (float)result[0];
            }
        }
        
        // 添加到历史记录
        performanceHistory.Add(data);
        
        // 限制历史记录大小
        if (performanceHistory.Count > maxSamples)
        {
            performanceHistory.RemoveAt(0);
        }
        
        // 重置计数器
        frameCount = 0;
        frameTimeTotal = 0;
    }
    
    // 检查性能阈值
    private void CheckPerformanceThresholds()
    {
        if (performanceHistory.Count == 0) return;
        
        var latestData = performanceHistory[performanceHistory.Count - 1];
        
        // FPS过低警告
        if (latestData.fps < 30)
        {
            Debug.LogWarning($"FPS过低: {latestData.fps:F1}");
            
            // 触发优化事件
            XLuaManager.Instance.CallLuaFunction(
                "PerformanceOptimizer.OnLowFPS", 
                latestData.fps);
        }
        
        // 帧时间过高警告
        if (latestData.frameTime > 33.3f) // 对应30FPS
        {
            Debug.LogWarning($"帧时间过高: {latestData.frameTime:F1}ms");
        }
        
        // 内存过高警告
        if (latestData.memoryUsage > 500) // 500MB
        {
            Debug.LogWarning($"内存使用过高: {latestData.memoryUsage:F1}MB");
        }
    }
    
    // 开始监控
    public void StartMonitoring()
    {
        enableMonitoring = true;
        
        // 注册Lua内存回调
        RegisterLuaMemoryCallback();
        
        Debug.Log("性能监控已启动");
    }
    
    // 停止监控
    public void StopMonitoring()
    {
        enableMonitoring = false;
        Debug.Log("性能监控已停止");
    }
    
    // 注册Lua内存回调
    private void RegisterLuaMemoryCallback()
    {
        // 从Lua获取内存使用情况的函数
        luaMemoryCallback = XLuaManager.Instance.CallLuaFunction(
            "PerformanceOptimizer.GetMemoryUsage")[0] as LuaFunction;
    }
    
    // 获取性能报告
    public PerformanceData GetPerformanceReport()
    {
        if (performanceHistory.Count == 0)
        {
            return new PerformanceData();
        }
        
        // 计算平均值
        PerformanceData average = new PerformanceData();
        
        foreach (var data in performanceHistory)
        {
            average.frameTime += data.frameTime;
            average.fps += data.fps;
            average.memoryUsage += data.memoryUsage;
            average.luaMemory += data.luaMemory;
        }
        
        int count = performanceHistory.Count;
        average.frameTime /= count;
        average.fps /= count;
        average.memoryUsage /= count;
        average.luaMemory /= count;
        
        return average;
    }
    
    // 获取性能历史
    public List<PerformanceData> GetPerformanceHistory()
    {
        return performanceHistory;
    }
    
    // 记录Lua性能数据
    public void RecordLuaPerformance(LuaTable luaPerformance)
    {
        // 将Lua性能数据整合到监控系统中
        Debug.Log($"Lua性能数据: {luaPerformance}");
    }
    
    // 性能优化建议
    public void GenerateOptimizationSuggestions()
    {
        var report = GetPerformanceReport();
        var suggestions = new List<string>();
        
        if (report.fps < 45)
        {
            suggestions.Add("建议优化渲染性能,当前FPS较低");
        }
        
        if (report.frameTime > 25)
        {
            suggestions.Add("建议优化CPU性能,帧时间过高");
        }
        
        if (report.memoryUsage > 400)
        {
            suggestions.Add("建议优化内存使用,内存占用过高");
        }
        
        if (report.luaMemory > 50)
        {
            suggestions.Add("建议优化Lua内存使用,考虑增加GC频率或优化数据结构");
        }
        
        // 发送建议到Lua
        XLuaManager.Instance.CallLuaFunction(
            "PerformanceOptimizer.ReceiveSuggestions", 
            suggestions);
    }
}

第八部分:实际项目案例

8.1 智能座舱仪表盘实现

-- InstrumentCluster.lua - 智能仪表盘控制器
local InstrumentCluster = {}
local _uiElements = {}
local _animations = {}
local _dataSubscriptions = {}
local _themeConfig = nil

-- 初始化仪表盘
function InstrumentCluster.Init(config)
    print("[LUA] 初始化智能仪表盘")
    
    -- 加载主题配置
    _themeConfig = InstrumentCluster.LoadTheme(config.theme or "default")
    
    -- 初始化UI元素
    InstrumentCluster.InitUIElements()
    
    -- 订阅车辆数据
    InstrumentCluster.SubscribeToVehicleData()
    
    -- 启动动画系统
    InstrumentCluster.StartAnimations()
    
    -- 注册事件
    EventManager.AddListener("ThemeChanged", InstrumentCluster.OnThemeChanged)
    EventManager.AddListener("DisplayModeChanged", InstrumentCluster.OnDisplayModeChanged)
end

-- 加载主题配置
function InstrumentCluster.LoadTheme(themeName)
    local themes = {
        default = {
            primaryColor = {0, 122, 255},
            secondaryColor = {255, 149, 0},
            backgroundColor = {0, 0, 0, 0.9},
            fontSize = 14,
            speedometerStyle = "modern"
        },
        
        sport = {
            primaryColor = {255, 59, 48},
            secondaryColor = {255, 149, 0},
            backgroundColor = {0, 0, 0, 1},
            fontSize = 16,
            speedometerStyle = "racing"
        },
        
        classic = {
            primaryColor = {52, 199, 89},
            secondaryColor = {175, 82, 222},
            backgroundColor = {0.1, 0.1, 0.1, 0.95},
            fontSize = 12,
            speedometerStyle = "analog"
        }
    }
    
    return themes[themeName] or themes.default
end

-- 初始化UI元素
function InstrumentCluster.InitUIElements()
    -- 通过C#获取UI元素引用
    _uiElements = {
        speedText = CS.UnityEngine.GameObject.Find("SpeedText"),
        rpmGauge = CS.UnityEngine.GameObject.Find("RPMGauge"),
        batteryGauge = CS.UnityEngine.GameObject.Find("BatteryGauge"),
        gearText = CS.UnityEngine.GameObject.Find("GearText"),
        rangeText = CS.UnityEngine.GameObject.Find("RangeText"),
        warningIcons = {},
        infoDisplays = {}
    }
    
    -- 应用主题
    InstrumentCluster.ApplyTheme()
end

-- 应用主题
function InstrumentCluster.ApplyTheme()
    -- 设置颜色
    InstrumentCluster.SetUIColor(_themeConfig.primaryColor, 
        _themeConfig.secondaryColor)
    
    -- 设置字体大小
    InstrumentCluster.SetFontSize(_themeConfig.fontSize)
    
    -- 设置速度表样式
    InstrumentCluster.SetSpeedometerStyle(_themeConfig.speedometerStyle)
    
    print("仪表盘主题已应用: " .. _themeConfig.speedometerStyle)
end

-- 订阅车辆数据
function InstrumentCluster.SubscribeToVehicleData()
    -- 订阅速度数据
    _dataSubscriptions.speed = VehicleDataManager.AddListener(
        "speed", 
        InstrumentCluster.OnSpeedChanged
    )
    
    -- 订阅电池数据
    _dataSubscriptions.battery = VehicleDataManager.AddListener(
        "batteryLevel", 
        InstrumentCluster.OnBatteryChanged
    )
    
    -- 订阅档位数据
    _dataSubscriptions.gear = VehicleDataManager.AddListener(
        "gearPosition", 
        InstrumentCluster.OnGearChanged
    )
    
    -- 订阅续航数据
    _dataSubscriptions.range = VehicleDataManager.AddListener(
        "estimatedRange", 
        InstrumentCluster.OnRangeChanged
    )
end

-- 速度变化回调
function InstrumentCluster.OnSpeedChanged(newSpeed, oldSpeed)
    -- 更新速度显示
    local speedText = string.format("%.0f", newSpeed)
    CS.CSharpToLuaBridge.Instance.UpdateText(
        _uiElements.speedText, 
        speedText
    )
    
    -- 更新速度表指针(如果有)
    InstrumentCluster.UpdateSpeedometer(newSpeed)
    
    -- 速度超过阈值时高亮显示
    if newSpeed > 120 then
        InstrumentCluster.HighlightSpeed()
    end
end

-- 电池变化回调
function InstrumentCluster.OnBatteryChanged(newLevel, oldLevel)
    -- 更新电池显示
    local batteryText = string.format("%.0f%%", newLevel)
    CS.CSharpToLuaBridge.Instance.UpdateText(
        _uiElements.batteryGauge, 
        batteryText
    )
    
    -- 更新电池图标
    InstrumentCluster.UpdateBatteryIcon(newLevel)
    
    -- 低电量警告
    if newLevel < 20 then
        InstrumentCluster.ShowLowBatteryWarning(newLevel)
    end
end

-- 更新电池图标
function InstrumentCluster.UpdateBatteryIcon(level)
    local iconName
    if level > 80 then
        iconName = "battery_full"
    elseif level > 50 then
        iconName = "battery_medium"
    elseif level > 20 then
        iconName = "battery_low"
    else
        iconName = "battery_critical"
    end
    
    CS.CSharpToLuaBridge.Instance.SetImage(
        _uiElements.batteryGauge, 
        "Icons/" .. iconName
    )
end

-- 显示低电量警告
function InstrumentCluster.ShowLowBatteryWarning(level)
    -- 播放警告动画
    AnimationController.PlayAnimation("WarningPulse", {
        target = _uiElements.batteryGauge,
        color = {255, 59, 48}
    })
    
    -- 显示警告消息
    CS.CSharpToLuaBridge.Instance.ShowNotification(
        "电量不足", 
        string.format("当前电量 %.0f%%,请及时充电", level),
        5000
    )
end

-- 档位变化回调
function InstrumentCluster.OnGearChanged(newGear, oldGear)
    -- 更新档位显示
    local gearDisplay = InstrumentCluster.FormatGearDisplay(newGear)
    CS.CSharpToLuaBridge.Instance.UpdateText(
        _uiElements.gearText, 
        gearDisplay
    )
    
    -- 档位变化动画
    AnimationController.PlayAnimation("GearChange", {
        target = _uiElements.gearText
    })
end

-- 格式化档位显示
function InstrumentCluster.FormatGearDisplay(gear)
    local gearMap = {
        ["P"] = "P",
        ["R"] = "R",
        ["N"] = "N",
        ["D"] = "D",
        ["S"] = "S"
    }
    
    return gearMap[gear] or gear
end

-- 续航变化回调
function InstrumentCluster.OnRangeChanged(newRange, oldRange)
    -- 更新续航显示
    local rangeText = string.format("%.0f km", newRange)
    CS.CSharpToLuaBridge.Instance.UpdateText(
        _uiElements.rangeText, 
        rangeText
    )
end

-- 启动动画系统
function InstrumentCluster.StartAnimations()
    -- 启动背景动画
    _animations.background = AnimationController.PlayAnimation(
        "BackgroundPulse", 
        {speed = 0.5, intensity = 0.1}
    )
    
    -- 启动数据更新动画
    InstrumentCluster.StartDataUpdateAnimation()
end

-- 启动数据更新动画
function InstrumentCluster.StartDataUpdateAnimation()
    local updateCoroutine = coroutine.create(function()
        while true do
            -- 每0.5秒轻微脉动一次(表示系统活动)
            AnimationController.PlayAnimation("DataUpdatePulse", {
                target = _uiElements.speedText,
                scale = 1.05,
                duration = 0.1
            })
            
            coroutine.wait(0.5)
        end
    end)
    
    coroutine.resume(updateCoroutine)
end

-- 主题变化回调
function InstrumentCluster.OnThemeChanged(newTheme)
    _themeConfig = InstrumentCluster.LoadTheme(newTheme)
    InstrumentCluster.ApplyTheme()
    
    print("仪表盘主题已切换为: " .. newTheme)
end

-- 显示模式变化回调
function InstrumentCluster.OnDisplayModeChanged(mode)
    -- 根据显示模式调整UI
    if mode == "minimal" then
        InstrumentCluster.ShowMinimalDisplay()
    elseif mode == "full" then
        InstrumentCluster.ShowFullDisplay()
    elseif mode == "navigation" then
        InstrumentCluster.ShowNavigationDisplay()
    end
end

-- 显示精简模式
function InstrumentCluster.ShowMinimalDisplay()
    -- 隐藏非必要元素
    CS.CSharpToLuaBridge.Instance.SetVisible(_uiElements.rangeText, false)
    CS.CSharpToLuaBridge.Instance.SetVisible(_uiElements.batteryGauge, false)
    
    -- 简化动画
    AnimationController.StopAnimation("BackgroundPulse")
end

-- 显示完整模式
function InstrumentCluster.ShowFullDisplay()
    -- 显示所有元素
    CS.CSharpToLuaBridge.Instance.SetVisible(_uiElements.rangeText, true)
    CS.CSharpToLuaBridge.Instance.SetVisible(_uiElements.batteryGauge, true)
    
    -- 恢复动画
    _animations.background = AnimationController.PlayAnimation(
        "BackgroundPulse", 
        {speed = 0.5, intensity = 0.1}
    )
end

-- 显示导航模式
function InstrumentCluster.ShowNavigationDisplay()
    -- 调整布局以优先显示导航信息
    InstrumentCluster.ShowMinimalDisplay()
    
    -- 添加导航相关信息显示
    -- ...
end

-- 设置UI颜色
function InstrumentCluster.SetUIColor(primaryColor, secondaryColor)
    CS.CSharpToLuaBridge.Instance.SetColor(
        _uiElements.speedText, 
        primaryColor[1], primaryColor[2], primaryColor[3]
    )
    
    CS.CSharpToLuaBridge.Instance.SetColor(
        _uiElements.gearText, 
        secondaryColor[1], secondaryColor[2], secondaryColor[3]
    )
end

-- 设置字体大小
function InstrumentCluster.SetFontSize(size)
    CS.CSharpToLuaBridge.Instance.SetFontSize(_uiElements.speedText, size)
    CS.CSharpToLuaBridge.Instance.SetFontSize(_uiElements.gearText, size - 2)
    CS.CSharpToLuaBridge.Instance.SetFontSize(_uiElements.rangeText, size - 2)
end

-- 设置速度表样式
function InstrumentCluster.SetSpeedometerStyle(style)
    -- 通过C#切换速度表预制体
    CS.CSharpToLuaBridge.Instance.SwitchSpeedometer(style)
end

-- 高亮速度显示
function InstrumentCluster.HighlightSpeed()
    -- 播放警告动画
    AnimationController.PlayAnimation("WarningPulse", {
        target = _uiElements.speedText,
        color = {255, 59, 48},
        duration = 0.5
    })
    
    -- 播放警告音效
    CS.CSharpToLuaBridge.Instance.PlaySound("warning_speed")
end

-- 更新速度表
function InstrumentCluster.UpdateSpeedometer(speed)
    -- 计算速度表角度(0-240度对应0-200km/h)
    local angle = math.min(240, speed / 200 * 240)
    
    CS.CSharpToLuaBridge.Instance.SetGaugeAngle(
        _uiElements.rpmGauge, 
        angle
    )
end

-- 清理资源
function InstrumentCluster.Cleanup()
    -- 取消数据订阅
    for _, unsubscribe in pairs(_dataSubscriptions) do
        if unsubscribe then unsubscribe() end
    end
    
    -- 停止动画
    for _, anim in pairs(_animations) do
        AnimationController.StopAnimation(anim)
    end
    
    print("仪表盘资源已清理")
end

return InstrumentCluster

总结

通过以上详细的代码实例,我们可以看到Unity引擎与Lua脚本在车机HMI开发中的深度融合:

  1. 架构清晰:通过C#桥接层实现了Unity与Lua的有效通信,保持了各层的职责分离
  2. 业务逻辑灵活:Lua层处理所有业务逻辑,实现了快速迭代和热更新
  3. UI动画丰富:利用Unity强大的动画系统和Lua的灵活控制,实现了丰富的交互效果
  4. 车控集成完善:通过CAN总线通信模块,实现了与车辆底层系统的双向通信
  5. 热更新可靠:完整的Lua热更新机制,支持远程修复和功能升级
  6. 性能优化全面:从Lua内存管理到Unity渲染优化,全方位的性能监控和优化策略

这种架构不仅满足了车机HMI对高性能渲染的需求,也满足了快速迭代和热更新的业务需求,是当前智能座舱开发的主流技术方案。开发团队可以根据具体项目需求,在这个框架基础上进行扩展和定制。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小宝哥Code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值