10分钟在UE里跑一个数据大屏

10分钟在UE里跑一个数据大屏

先说结论:你不需要懂前端,也不需要写 C++,10 分钟就能在 UE 里跑一个带实时图表、设备列表、告警面板的数据大屏,和三维场景联动。 跟着下面四步走,每一步都有完整代码,复制粘贴就能跑。

WebNativeBrowser 是面向 UE 5.1–5.8 的高性能企业级跨平台 Web UI 插件,提供 GPU 直通渲染、双向消息通道、透明交互和开箱即用的演示页面。
在这里插入图片描述


第 1 步:安装插件(2 分钟)

  1. 从 GitHub Release 下载你 UE 版本对应的 zip
  2. 解压到项目的 Plugins/ 目录下
  3. 目录名改成 WebNativeBrowser(⚠️ 必须是这个名字)
  4. 重新打开项目
YourProject/
├── Plugins/
│   └── WebNativeBrowser/       ← 放这里
├── Content/
└── YourProject.uproject

打开项目后,编辑器会提示重新编译。编译完成即安装成功。


第 2 步:创建数据大屏页面(3 分钟)

YourProject/Content/WebUI/ 下创建 dashboard.html,复制以下代码:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.0/dist/echarts.min.js"></script>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    html, body { width: 100%; height: 100%; background: transparent; font-family: "Microsoft YaHei", sans-serif; }
    
    .dashboard {
      display: grid;
      grid-template-columns: 300px 1fr 1fr;
      grid-template-rows: 80px 1fr 1fr;
      gap: 12px;
      width: 100%;
      height: 100%;
      padding: 16px;
    }

    .header {
      grid-column: 1 / -1;
      background: linear-gradient(135deg, rgba(0,20,60,0.9), rgba(0,40,100,0.9));
      border: 1px solid rgba(0,150,255,0.3);
      border-radius: 8px;
      display: flex; align-items: center; padding: 0 32px;
    }
    .header h1 { color: #00e5ff; font-size: 28px; letter-spacing: 4px; }

    .device-panel {
      grid-row: 2 / 4;
      background: rgba(0,20,50,0.85);
      border: 1px solid rgba(0,150,255,0.3);
      border-radius: 8px;
      padding: 16px;
      overflow-y: auto;
    }
    .device-panel h3 { color: #00e5ff; margin-bottom: 12px; }

    .device-item {
      display: flex; justify-content: space-between; align-items: center;
      padding: 10px 12px; margin-bottom: 6px;
      background: rgba(0,100,200,0.15);
      border-radius: 6px;
      cursor: pointer;
      transition: all 0.2s;
    }
    .device-item:hover { background: rgba(0,150,255,0.3); }
    .device-item .name { color: #e0e0e0; font-size: 14px; }
    .device-item .status { font-size: 12px; padding: 2px 8px; border-radius: 4px; }
    .status-running { background: rgba(0,200,100,0.3); color: #4cff8d; }
    .status-alarm   { background: rgba(255,50,50,0.3);  color: #ff6b6b; }
    .status-stopped { background: rgba(150,150,150,0.3); color: #aaa; }

    .chart-box {
      background: rgba(0,20,50,0.85);
      border: 1px solid rgba(0,150,255,0.3);
      border-radius: 8px;
      overflow: hidden;
    }

    .alarm-panel {
      background: rgba(0,20,50,0.85);
      border: 1px solid rgba(0,150,255,0.3);
      border-radius: 8px;
      padding: 16px;
    }
    .alarm-panel h3 { color: #ff6b6b; margin-bottom: 12px; }
    .alarm-item {
      padding: 8px 12px; margin-bottom: 4px;
      background: rgba(255,50,50,0.12);
      border-left: 3px solid #ff4444;
      border-radius: 4px;
      color: #ff9999; font-size: 13px;
    }
    .alarm-item .time { color: #888; font-size: 11px; }
  </style>
</head>
<body>
  <div class="dashboard">
    <!-- 顶部标题栏 -->
    <div class="header">
      <h1>智慧工厂 · 实时监控大屏</h1>
    </div>

    <!-- 左侧设备列表 -->
    <div class="device-panel">
      <h3>设备列表</h3>
      <div id="device-list"></div>
    </div>

    <!-- 中间实时温度趋势图 -->
    <div class="chart-box" id="chart-temperature"></div>

    <!-- 右上产能柱状图 -->
    <div class="chart-box" id="chart-output"></div>

    <!-- 右下告警面板 -->
    <div class="alarm-panel">
      <h3>实时告警</h3>
      <div id="alarm-list"></div>
    </div>
  </div>

  <script>
    // ========== 模拟数据 ==========
    const devices = [
      { id: 'd001', name: '1号注塑机', status: 'running', temp: 185, output: 42 },
      { id: 'd002', name: '2号注塑机', status: 'running', temp: 192, output: 38 },
      { id: 'd003', name: '3号冲压机', status: 'alarm',  temp: 245, output: 15 },
      { id: 'd004', name: '4号冲压机', status: 'running', temp: 178, output: 40 },
      { id: 'd005', name: '5号机械臂', status: 'stopped', temp: 32,  output: 0 },
      { id: 'd006', name: '6号机械臂', status: 'running', temp: 45,  output: 28 },
    ];

    const alarms = [
      { device: '3号冲压机', msg: '温度超过安全阈值 (245°C)', time: '14:32:15' },
      { device: '5号机械臂', msg: '设备离线超过 30 分钟', time: '14:28:01' },
    ];

    // ========== 渲染设备列表 ==========
    function renderDevices() {
      const container = document.getElementById('device-list');
      container.innerHTML = devices.map(d => `
        <div class="device-item" onclick="onDeviceClick('${d.id}')">
          <span class="name">${d.name}</span>
          <span class="status status-${d.status}">${
            d.status === 'running' ? '运行中' :
            d.status === 'alarm'  ? '告警' : '已停机'
          }</span>
        </div>
      `).join('');
    }

    function onDeviceClick(deviceId) {
      const device = devices.find(d => d.id === deviceId);
      // 通知 UE:用户在网页中点击了设备
      WebNative.send("Dashboard.Device.Click", {
        deviceId: device.id,
        deviceName: device.name,
        status: device.status
      });
    }

    // ========== 渲染告警列表 ==========
    function renderAlarms() {
      const container = document.getElementById('alarm-list');
      container.innerHTML = alarms.map(a => `
        <div class="alarm-item">
          <div><strong>${a.device}</strong> — ${a.msg}</div>
          <div class="time">${a.time}</div>
        </div>
      `).join('');
    }

    // ========== 温度趋势图 (ECharts) ==========
    function renderTemperatureChart() {
      const chart = echarts.init(document.getElementById('chart-temperature'));
      const runningDevices = devices.filter(d => d.status === 'running');
      
      chart.setOption({
        backgroundColor: 'transparent',
        title: { text: '设备温度趋势', left: 16, top: 12, textStyle: { color: '#00e5ff', fontSize: 14 } },
        tooltip: { trigger: 'axis' },
        legend: { bottom: 8, textStyle: { color: '#aaa' } },
        grid: { top: 60, bottom: 40, left: 50, right: 20 },
        xAxis: {
          type: 'category',
          data: ['14:00','14:05','14:10','14:15','14:20','14:25','14:30'],
          axisLine: { lineStyle: { color: '#333' } },
          axisLabel: { color: '#888' },
        },
        yAxis: {
          type: 'value', name: '°C',
          axisLine: { lineStyle: { color: '#333' } },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.06)' } },
          axisLabel: { color: '#888' },
        },
        series: runningDevices.map(d => ({
          name: d.name,
          type: 'line',
          smooth: true,
          data: Array.from({length: 7}, () => d.temp + Math.floor(Math.random() * 20 - 10)),
          showSymbol: false,
          lineStyle: { width: 2 },
        })),
      });

      window.addEventListener('resize', () => chart.resize());
    }

    // ========== 产能柱状图 (ECharts) ==========
    function renderOutputChart() {
      const chart = echarts.init(document.getElementById('chart-output'));
      
      chart.setOption({
        backgroundColor: 'transparent',
        title: { text: '今日产能 (件)', left: 16, top: 12, textStyle: { color: '#00e5ff', fontSize: 14 } },
        tooltip: { trigger: 'axis' },
        grid: { top: 60, bottom: 30, left: 50, right: 20 },
        xAxis: {
          type: 'category',
          data: devices.map(d => d.name),
          axisLabel: { color: '#888', rotate: 20 },
          axisLine: { lineStyle: { color: '#333' } },
        },
        yAxis: {
          type: 'value',
          axisLabel: { color: '#888' },
          splitLine: { lineStyle: { color: 'rgba(255,255,255,0.06)' } },
        },
        series: [{
          type: 'bar',
          data: devices.map(d => ({
            value: d.output,
            itemStyle: {
              color: d.status === 'alarm' ? '#ff4444' :
                     d.status === 'running' ? '#00b4d8' : '#555'
            }
          })),
          barWidth: 24,
        }],
      });

      window.addEventListener('resize', () => chart.resize());
    }

    // ========== 监听 UE 消息 ==========
    WebNative.on("Scene.Device.Focus.Result", (messageBody) => {
      const { deviceId } = JSON.parse(messageBody);
      // UE 回复:镜头已聚焦到设备,前端可以做高亮
      console.log('UE 已聚焦设备:', deviceId);
    });

    // ========== 初始化 ==========
    renderDevices();
    renderAlarms();
    renderTemperatureChart();
    renderOutputChart();
  </script>
</body>
</html>

第 3 步:在 UE 中加载(3 分钟)

3.1 创建 Widget Blueprint

  1. Content Browser 右键 → User Interface → Widget Blueprint
  2. 命名为 WBP_DataDashboard
  3. 打开,从 Palette 搜索 WebNative Browser,拖入 Canvas

3.2 设置加载路径

选中 WebNative Browser 控件,在 Details 面板设置:

属性
Initial URLE:/YourProject/Content/WebUI/dashboard.html

⚠️ 路径改成你的实际项目路径。

3.3 绑定消息事件

  1. 选中 WebNative Browser 控件
  2. Details 面板底部找到 Events,点击 On Message Received 旁边的 +
  3. 蓝图会自动创建 Event Graph 节点

3.4 显示到视口

打开 Level Blueprint 或任意 Actor 蓝图,在 BeginPlay 添加:

Event BeginPlay
  → Create Widget (Class: WBP_DataDashboard)
  → Add to Viewport

第 4 步:跑起来并添加 UE 联动(2 分钟)

点击 Play,看到数据大屏显示在 UE 视口中。

现在加上双向联动——点击设备列表中的设备,UE 端收到消息:

WBP_DataDashboard 的 Event Graph 中:

Event On Message Received (FunctionName, MessageBody)
  → Branch (FunctionName == "Dashboard.Device.Click")
    → Print String (MessageBody)    ← 先打印看看收到什么

Play 后点击网页中的设备条目,UE 的 Output Log 会打印:

{"deviceId":"d003","deviceName":"3号冲压机","status":"alarm"}

到此,Web 数据大屏 + UE 双向通信的完整链路就跑通了。


接下来可以做什么

这个 Demo 对应 3 个真实生产场景的入口:

方向扩展方式
数据实时化把模拟数据换成 WebSocket 实时推送的生产数据
三维联动UE 收到 Dashboard.Device.Click 后,镜头飞到对应设备并高亮
透明叠加把面板背景改成透明,叠加在 UE 三维场景上,面板间隙可以看到场景

WebNativeBrowser 让这个过程简单在哪

如果没有 WebNativeBrowser用 WebNativeBrowser
自己编译 CEF、处理多平台二进制下载 zip,放进 Plugins,完成
自己处理 Web → UE 消息通道三行 JS + 一个蓝图事件
GPU 渲染手动配置、怕依赖没打进包自动识别 GPU 后端,自动打包运行时文件
白屏了抓瞎,没有 DevToolsCtrl+F12 一键打开 Chrome DevTools
Linux/ARM64 平台自己踩坑已完成国产 CPU/GPU/系统适配

如果这篇帮你 10 分钟跑通了数据大屏,欢迎点赞收藏。WebNativeBrowser 支持 UE 5.1–5.8,覆盖 Windows / Linux x86_64 / Linux ARM64,已完成国产 GPU 专项适配。

GitHub:https://github.com/starTechnology1994/uewebbrowser

商务合作 / 授权咨询:startechnology1994@163.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值