VR場景切換-微信小程序

業務需求

  1. 使用UnrealEngine4開發一套基於VR一體機(安卓系統)的應用程序
  2. 內容爲720VR場景瀏覽,通過控制器在菜單中切換圖標來控制切換相應場景,效果如下圖
    在這裏插入圖片描述
  3. 因爲VR一體機瀏覽只有客戶能看到場景內容,需開發一個微信小程序幫助操控切換場景引導客戶瀏覽,微信小程序效果如下
    在這裏插入圖片描述

開發軟件

  1. UnrealEngine4
  2. 微信小程序

技術方案

方案1:通過WebSocket通信(因爲需要後端,開發流程較長,所以不採用)
方案2:通過TCP通信(因爲微信小程序不支持,所以不採用)
方案3:通過UDP通信(因爲需求很簡單又在局域網範圍,所以採用)

  1. 開啓VR一體機中的VR應用程序,初始化創建UdpSocket固定綁定端口號(如:10000),獲取當前設備的IP,並顯示IP(如:192.168.19.106)到界面上以便後續微信小程序發送消息所用
  2. 開啓微信小程序,初始創建UdpSocket綁定隨機端口號,在IP輸入欄中輸入VR一體機界面中顯示的IP號,併發送消息到 192.168.19.106:10000,若成功收到返回數據則表示通信正常
  3. 自定義通信數據,區分消息指令,實現各類功能
  4. 微信小程序項目中準備好相應素材(場景縮略圖,數據文件等)
    在這裏插入圖片描述

技術文檔

  1. 微信小程序項目所需的data.js
var projects = [
	{
		project_id:0,
		project_name:"BingFengMobile",
		scenes:[
			{
				scene_id:0,
				title:"前臺",
				image:"BingFengMobile/images/reception.png",
			},
			{
				scene_id: 1,
				title: "展廳",
				image: "BingFengMobile/images/showroom.png",
			},
			{
				scene_id: 2,
				title: "入口",
				image: "BingFengMobile/images/enter.png",
			},
			{
				scene_id: 3,
				title: "休息區",
				image: "BingFengMobile/images/restroom.png",
			},
			{
				scene_id: 4,
				title: "工作區1",
				image: "BingFengMobile/images/work1.png",
			},
			{
				scene_id: 5,
				title: "工作區2",
				image: "BingFengMobile/images/work2.png",
			},
			{
				scene_id: 6,
				title: "工作區3",
				image: "BingFengMobile/images/work3.png",
			},
			{
				scene_id: 7,
				title: "會議室",
				image: "BingFengMobile/images/meetingroom.png",
			},
			{
				scene_id: 8,
				title: "走廊",
				image: "BingFengMobile/images/corridor.png",
			},
			{
				scene_id: 9,
				title: "電梯口",
				image: "BingFengMobile/images/elevator.png",
			}
		]
	},
	{
		project_id:1,
		project_name:"PaiMaiHang",
		scenes:[
			{
				scene_id:0,
				title:"默認",
				image:"",
			}
		]
	}
]

module.exports={
	projects:projects
}
  1. 自定義通信數據協議
/* 
* UDP發送字符串
* 字符串格式:"xxx:xxx"
*/
// 小程序發送通信連接消息內容:
"connect:request"
//VR程序回覆通信連接消息內容:
“connect:ok”

// 小程序發送場景切換消息內容:
“scene:3” //注意:此處的3爲場景的對應id
//若需要VR切換場景做迴應則同上即可

微信小程序項目關鍵代碼

app.json

//app.json
{
  "pages": [
    "pages/index/index",
    "pages/mine/mine"
  ],
  "window": {
    "backgroundTextStyle": "light",
    "navigationBarBackgroundColor": "#fff",
    "navigationBarTitleText": "WeChat",
    "navigationBarTextStyle": "black"
  },
  "tabBar": {
    "color": "#999999",
    "selectedColor": "#006AB4",
    "backgroundColor": "#EEEEEE",
    "borderStyle":"white",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首頁",
        "iconPath": "./icons/home.png",
        "selectedIconPath": "./icons/home_s.png"
      },
      {
        "pagePath": "pages/mine/mine",
        "text": "我的",
        "iconPath": "./icons/mine.png",
        "selectedIconPath": "./icons/mine_s.png"
      }
    ]
  },
  "style": "v2",
  "sitemapLocation": "sitemap.json"
}

app.js

// app.js
App({
  globalData:{
    udpSocket: null,
    locationPort: -1
  }
})

index.json

// index.json
{
  "navigationBarTitleText":"冰峯科技VR中控",
  "usingComponents": {
    "Connector":"../../components/Connector/Connector"
  }
}

index.js

// index.js
var projectData = require("../../data/data.js")
let util = require('../../utils/util.js')
var app = getApp()

Page({
  /**
   * 頁面的初始數據
   */
  data: {
    // UDP
    ip: "192.168.19.100",
    port: 10000,
    connectState: false,

    // 項目數據
    projects: null,
    project: {
      project_id: 0,
      project_name: "BingFengMobile",
      scenes: null
    },

    // 被點擊的場景索引
    curSceneIndex: 0
  },

  initUDP() {
    // 檢測是否已經初始化udpSocket
    if (app.globalData.udpSocket != null) {
      this.setData({
        'locationUrl.port': app.globalData.locationPort
      })
      return
    }

    app.globalData.udpSocket = wx.createUDPSocket();
    if (app.globalData.udpSocket === null) {
      console.log('udpSocket創建失敗')
      return
    }

    app.globalData.locationPort = app.globalData.udpSocket.bind()
    this.setData({
      'locationUrl.port': app.globalData.locationPort
    })

    app.globalData.udpSocket.onListening(res => {

    })

    app.globalData.udpSocket.onMessage(res => {
      console.log(res.message)
      let encodedString = util.newAb2Str(res.message)
      console.log(encodedString)
      let keyValue = encodedString.split(":")
      if (keyValue[0] === "connect") {
        if (keyValue[1] == "ok") {
          setTimeout(() => {
            this.setData({
              connectState: true
            })
          }, 500);
        }
      } else if (keyValue[0] === "scene") {

      }
    })
  },

  // 發送UDP消息 
  sendMessage(message) {
    let ip = this.data.ip
    let port = this.data.port

    app.globalData.udpSocket.send({
      address: ip,
      port: port,
      message: message
    })
  },

  // ip輸入改變
  bindIpChanged(e) {
    this.setData({
      ip: e.detail.ip
    })
    console.log(this.data.ip)
  },

  // 點擊連接設備
  connectDevice(e) {
    // console.log("connectDevice")
    this.setData({
      connectState: false
    })
    this.sendMessage("connect:request")
  },

  // 點擊首頁場景
  activeScene(e) {
    this.setData({
      curSceneIndex: e.currentTarget.dataset.index
    })
    let message = "scene:" + this.data.curSceneIndex;
    this.sendMessage(message)
  },

  getProjectInfo(id) {
    for (let index in this.data.projects) {
      if (this.data.projects[index].project_id == id) {
        return this.data.projects[index]
      }
    }
  },

  /**
   * 生命週期函數--監聽頁面加載
   */
  onLoad: function(options) {
    this.initUDP()

    this.setData({
      projects: projectData.projects
    })

    if (this.data.projects != null) {
      //獲取項目id爲0的項目信息,並賦值給 project
      this.setData({
        project: this.getProjectInfo(0)
      })
    }
  },

  /**
   * 生命週期函數--監聽頁面初次渲染完成
   */
  onReady: function() {},

  /**
   * 生命週期函數--監聽頁面顯示
   */
  onShow: function() {},

  /**
   * 生命週期函數--監聽頁面隱藏
   */
  onHide: function() {},

  /**
   * 生命週期函數--監聽頁面卸載
   */
  onUnload: function() {},

  /**
   * 頁面相關事件處理函數--監聽用戶下拉動作
   */
  onPullDownRefresh: function() {},

  /**
   * 頁面上拉觸底事件的處理函數
   */
  onReachBottom: function() {},

  /**
   * 用戶點擊右上角分享
   */
  onShareAppMessage: function() {}
})

index.wxml

<!-- index.wxml -->
<view class="main">
  <!-- 首頁 設備連接 -->
  <Connector ip="{{ip}}" connectState="{{connectState}}" bind:ipEvent="bindIpChanged" bind:connectEvent="connectDevice"></Connector>

  <view class="scene_wrap">
    <view bindtap="activeScene" data-index="{{index}}" class="scene_item {{index===curSceneIndex? 'active':''}}" wx:for="{{project.scenes}}" wx:key="{{index}}">
      <!-- 圖片 -->
      <view class="scene_img">
        <image mode="widthFix" src="../../data/{{item.image}}" lazy-load></image>
        <!-- 標題 -->
        <view class="scene_title_wrap">
          <text class="scene_title">{{item.title}}</text>
        </view>
      </view>
    </view>
  </view>
</view>

index.wxss

/*index.wxss*/
.scene_wrap{
  display:flex;
  flex-wrap: wrap;
  padding:5rpx;
  margin: 20rpx;
  justify-content: space-between;
}

.scene_item{
  width:32%;
  margin-bottom: 10rpx;
  border:5rpx solid rgba(0, 0, 0, 0);
}

.scene_item.active{
  border:4rpx solid #EF8536;
}

.scene_img{
  position: relative;
}
.scene_img image{
  width:100%;
}

.scene_title_wrap{
  position: absolute;
  bottom:0;
  left:0;
  width:100%;
  background: rgba(7, 0, 2, 0.5);
  display: flex;
  justify-content:center;
}

.scene_title{
  color:white;
  font-size: 28rpx;
  padding: 5rpx;
}

Connector.js

// components/Connector.js
Component({
  /**
   * 組件的屬性列表
   */
  properties: {
    ip: String,
    connectState:Boolean
  },

  /**
   * 組件的初始數據
   */
  data: {},

  /**
   * 組件的方法列表
   */
  methods: {
    bindIpInput(e) {
      let val = e.detail.value
      this.triggerEvent('ipEvent', {ip: val})
    },
    onConnect(e){
      this.triggerEvent('connectEvent')
    }
  }
})

Connector.wxml

<!-- components/Connector.wxml -->
<view class="my_connector">
  <view class="content">
    <view class="input-btn-group">
      <!-- 地址輸入框-->
      <input class="text-input" auto-focus maxlength="15" bindinput="bindIpInput" value="{{ip}}"></input>
      <!-- 連接按鈕 -->
      <view hover-class="scale_img">
        <image src="./connect.png" class="btn-connect" bindtap="onConnect"></image>
      </view>
    </view>
  </view>

  <view class="content content_state">
    <image src="{{connectState? './state_1.png' : './state_0.png'}}" class="img-state"></image>
    <text class="connect_state_tab">連接狀態:</text>
    <text class="connect_state_value">{{connectState? '已連接' : '未連接'}}</text>
  </view>
</view>

Connector.wxss

/*components/Connector.wxss*/
.content {
  flex-basis: fit-content;
  display: flex;
  flex-direction: row;
  padding: 10rpx 30rpx;
  background: #fff;
}

.content .input-btn-group {
  flex: 1;
  display: flex;
  flex-direction: row;
}

.input-btn-group .text-input {
  flex: 1;
  font-size: 16px;
  height: 30px;
  border: 1px solid transparent;
  border-radius: 5px;
  padding: 3px 6px;
  margin: 0 10px 0 5px;
  background: #eee;
}

.input-btn-group .btn-connect {
  width: 80rpx;
  height: 80rpx;
  align-self: center;
}

.content_state {
  display: flex;
  flex-direction: row;
}

.scale_img {
  transform: scale(0.9, 0.9);
}

.img-state {
  width: 50rpx;
  height: 50rpx;
  color:red;
}

總結

  1. 微信小程序的UDP接收消息onMessage(res=>{})裏,PC調試時需要用res.message.data獲取數據,而真機調試或真機中需要用res.message才能獲取數據。
  2. data.js中的縮略圖名爲中文時,PC調試可以正常顯示,真機中圖片卻丟失了,於是保證圖片路徑中爲英文,不過也有可能是data.js文件編碼問題,不過沒有測試轉碼後的結果。
  3. 測試時幾次忘記手機需要與VR設備連接同一wifi保證在同一局域網,導致UDP消息接收不到,因爲微信小程序UDP只支持局域網。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章