VR場景切換-微信小程序
業務需求
- 使用UnrealEngine4開發一套基於VR一體機(安卓系統)的應用程序
- 內容爲720VR場景瀏覽,通過控制器在菜單中切換圖標來控制切換相應場景,效果如下圖
- 因爲VR一體機瀏覽只有客戶能看到場景內容,需開發一個微信小程序幫助操控切換場景引導客戶瀏覽,微信小程序效果如下
開發軟件
- UnrealEngine4
- 微信小程序
技術方案
方案1:通過WebSocket通信(因爲需要後端,開發流程較長,所以不採用)
方案2:通過TCP通信(因爲微信小程序不支持,所以不採用)
方案3:通過UDP通信(因爲需求很簡單又在局域網範圍,所以採用)
- 開啓VR一體機中的VR應用程序,初始化創建UdpSocket固定綁定端口號(如:10000),獲取當前設備的IP,並顯示IP(如:192.168.19.106)到界面上以便後續微信小程序發送消息所用
- 開啓微信小程序,初始創建UdpSocket綁定隨機端口號,在IP輸入欄中輸入VR一體機界面中顯示的IP號,併發送消息到 192.168.19.106:10000,若成功收到返回數據則表示通信正常
- 自定義通信數據,區分消息指令,實現各類功能
- 微信小程序項目中準備好相應素材(場景縮略圖,數據文件等)
技術文檔
- 微信小程序項目所需的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
}
- 自定義通信數據協議
/*
* 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;
}
總結
- 微信小程序的UDP接收消息onMessage(res=>{})裏,PC調試時需要用res.message.data獲取數據,而真機調試或真機中需要用res.message才能獲取數據。
- data.js中的縮略圖名爲中文時,PC調試可以正常顯示,真機中圖片卻丟失了,於是保證圖片路徑中爲英文,不過也有可能是data.js文件編碼問題,不過沒有測試轉碼後的結果。
- 測試時幾次忘記手機需要與VR設備連接同一wifi保證在同一局域網,導致UDP消息接收不到,因爲微信小程序UDP只支持局域網。