更新时间:2025-01-10 GMT+08:00
开发指导
开发流程
图1 开启AR会话开发流程
开发过程
- 开启AR相机。
- 在工具的资源管理器,单击右键,选择“新建文件夹”,命名为“components”。
- 选择“components”文件夹,单击右键,选择“新建文件夹”,命名为“xrstart”。
- 选择“xrstart”文件夹,单击右键,选择“新建Component”,命名为“xrstart”。
Component建完后,目录结构如图2。
- 在xrstart.json文件中,添加renderer配置项。
{ "component": true, "renderer": "xr-frame", "usingComponents": {} }
- 在xrstart.wxml文件中,添加camera、light节点。
<xr-scene ar-system="modes:Plane;" > <xr-node node-id="cameraParent" position="0 0 0" rotation="0 0 0"> <xr-camera id="camera" node-id="camera" clear-color="0.925 0.925 0.925 1" background="ar" far="2000" is-ar-camera></xr-camera> </xr-node> <xr-node node-id="lights"> <xr-light type="ambient" color="1 1 1" intensity="1" /> <xr-light type="directional" rotation="180 0 0" color="1 1 1" intensity="3" /> </xr-node> </xr-scene>
- 在index.json文件中,引用新建的xrstart组件。
{ "usingComponents": { "xr-start": "../../components/xrstart/xrstart" } }
- 在index.js文件中,配置高度和宽度参数。
import { XRClient } from "../../build/XRClient"; Page({ data: { width: 300, height: 300, renderWidth: 300, renderHeight: 300 }, onLoad() { console.log(XRClient.getVersion()) const info = wx.getSystemInfoSync(); const width = info.windowWidth; const height = info.windowHeight; const dpi = info.pixelRatio; this.setData({ width, height, renderWidth: width * dpi, renderHeight: height * dpi }); } })
- 在index.wxml中设置xr-start组件高度和宽度。
<view> <xr-start disable-scroll id="main-frame" width="{{renderWidth}}" height="{{renderHeight}}" style="width:{{width}}px;height:{{height}}px;" /> </view>
- 在app.json中配置lazyCodeLoading。
{ "pages": [ "pages/index/index" ], "lazyCodeLoading": "requiredComponents" }
- 单击“真机调试”,小程序会开启AR相机,在手机上可看到相机拍摄到的现实环境画面。
图3 开启AR相机
- 接入视觉定位。
- 在工具的资源管理器,单击右键,选择“新建文件夹”,命名为“utils”。
- 选择utils文件夹,单击右键,选择“新建文件”,命名为“GlobalBus.ts”。
- 在GlobalBus.ts文件中,配置事件码。
export class GlobalBus { //vps定位结果事件码 public static VPS_RESULT: string = 'VPS_RESULT'; //vps状态事件码 public static VPS_TRACKING: string = 'VPS_TRACKING'; //scene实例事件码 public static SCENE_INSTANCE: string = 'SCENE_INSTANCE'; }
- 选择utils文件夹,单击右键,选择“新建文件”,命名为“Logger.ts”。
- 在Logger.ts文件中,实现ILog接口。
export class Logger implements ILog { private logLevel: number = 3; private isSaveLog: boolean = false; private logSaveFolderPath: string = `${wx.env.USER_DATA_PATH}/miniprogramARSDK/`; private logSaveFileName: string = ""; init() { let time:any= this.formatTime(new Date()) time=time.replaceAll("/","_").replaceAll(" ","_").replaceAll(":","_") this.logSaveFileName=time+".log" this.createLogFile(); } setIsSaveLog(isSave: boolean) { this.isSaveLog = isSave; } setLogOutputLevel(level: number) { this.logLevel = level; } error<T>(message: T) { if (this.logLevel < 1) return; const formartLog= this.formatLog("ERROR",message) console.error(formartLog); this.logToFile( formartLog); } warn<T>(message: T) { if (this.logLevel < 2) return; const formartLog= this.formatLog("WARN",message) console.warn(formartLog); this.logToFile( formartLog); } log<T>(message: T) { if (this.logLevel < 3) return; const formartLog= this.formatLog("LOG",message) console.log(formartLog); this.logToFile(formartLog); } createLogFile() { if(!this.isSaveLog)return; let self = this; let fsm = wx.getFileSystemManager() fsm.access({ path: self.logSaveFolderPath, success() { }, fail() { fsm.mkdirSync(self.logSaveFolderPath, true) } }) fsm.access({ path: self.logSaveFolderPath + self.logSaveFileName, success() { }, fail() { fsm.writeFileSync(self.logSaveFolderPath + self.logSaveFileName, "", `utf-8`); } }) console.log(`create logfile:${self.logSaveFolderPath + self.logSaveFileName}`) } logToFile(message: string) { if (!this.isSaveLog) return; let fsm = wx.getFileSystemManager() let self = this; fsm.access({ path: self.logSaveFolderPath + self.logSaveFileName, complete() { fsm.appendFile({ filePath: self.logSaveFolderPath + self.logSaveFileName, data: message, encoding: 'utf8', success: () => { }, fail: (res) => { console.log(res.errMsg); } }) } }) } formatLog<T>(level: string, message: T): string { const date = new Date(); const formattedDate=this.formatTime(date) const formattedMessage: string = `[${formattedDate}][${level}]: ${message}\n`; return formattedMessage } formatNumber = (n: number) => { const s = n.toString() return s[1] ? s : '0' + s } formatTime = (date: Date) => { const year = date.getFullYear() const month = date.getMonth() + 1 const day = date.getDate() const hour = date.getHours() const minute = date.getMinutes() const second = date.getSeconds() return ( [year, month, day].map(this.formatNumber).join('/') + ' ' + [hour, minute, second].map(this.formatNumber).join(':') ) } }
- 在app.js文件中初始化logger。
import { Logger } from "./utils/Logger"; import { XRClient } from "./build/XRClient"; App({ onLaunch() { const logger=new Logger() XRClient.initLogger(logger) XRClient.log('onLaunch'); }, })
- 在xrstart.js文件中,初始化Vps,侦听Vps结果并设置回调函数。
import { XRClient } from "../../build/XRClient"; import { GlobalBus } from "../../utils/GlobalBus"; Component({ methods: { handleReady({ detail }) { this.scene = detail.value; this.offsetSetted = false; XRClient.log("handleReady"); }, handleARReady: function ({ }) { XRClient.log('arReady: arVersion ' + this.scene.ar.arVersion); XRClient.log('arReady: version ' + this.scene.ar.version); // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; // 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 let config = { access: process.env.HUAWEICLOUD_SDK_AK, secret: process.env.HUAWEICLOUD_SDK_SK, beforeLocateDelta: 15, afterLocateDelta: 60, autoRequest: true, workerPath: 'build/workers/ImageProcessWorker.js', url:'' } XRClient.init(config); XRClient.dispatchEvent(GlobalBus.SCENE_INSTANCE, { info: this.scene }); XRClient.addEvent(GlobalBus.VPS_RESULT, this.onVpsResult, this); this.arInited = true; }, handleTick() { XRClient.updatePerFrame(); }, onVpsResult(evt) { XRClient.log("onVpsResult status" + evt.data.status); XRClient.log("onVpsResult info" + evt.data.info); if (evt.data.status) { XRClient.log("VPS success"); wx.showToast({ title: '定位成功', icon: 'success', duration: 2000 }) } else { XRClient.log("VPS failure"); wx.showToast({ title: '定位失败', icon: 'none', duration: 2000 }) } }, }, })
代码中url固定为:https://koomap.cn-north-4.myhuaweicloud.com。
- 在xrstart.wxml文件里绑定函数。
<xr-scene ar-system="modes:Plane;" bind:ready="handleReady" bind:ar-ready="handleARReady" bind:tick="handleTick">
- 在app.json文件里添加地理位置permission并配置workers。
{ "pages": [ "pages/index/index" ], "permission": { "scope.userLocation": { "desc": "为了给您提供更好的服务,请授权您的地理位置信息" } }, "workers": "build/workers", "lazyCodeLoading": "requiredComponents" }
- 在project.config.json文件的setting中,配置useCompilerPlugins。
"useCompilerPlugins": [ "typescript" ]
- 单击“真机调试”,小程序会自动发起定位,根据定位结果后,会弹出Toast提示是否定位成功。如定位失败,可参见常见问题排查章节确定原因。
- 渲染数字内容。
- 选择utils文件夹,单击右键,选择“新建文件”,命名为“ModelMag.ts”
- 在“ModelMag.ts”文件中,添加渲染逻辑。
import { XRClient } from "../build/XRClient"; import { GlobalBus } from "./GlobalBus"; let xrFrameSystem class ModelMag { private scene: any; modelRoot: any; gltfModel: any[] = []; digitalInfo: any; count = 0; private utmPositionJson = "[{\"position\":{\"x\":0,\"y\":0,\"z\":0}},{\"position\":{\"x\":0,\"y\":0,\"z\":2}}]"; //在utmPositionJson中配置数字内容的坐标,定位成功后接口会返回用户当前位置的坐标信息,可以将数字内容坐标设置在用户附近,以便于调试。 constructor(scene: any) { this.scene = scene; xrFrameSystem = wx.getXrFrameSystem(); const jsonData = JSON.parse(this.utmPositionJson); let node = this.scene.createElement(xrFrameSystem.XRNode); this.modelRoot = node; this.scene.addChild(node); this.digitalInfo = jsonData; this.setCube(); XRClient.addEvent(GlobalBus.VPS_TRACKING, this.onVpsTrackingState, this); } public setPosition(xOffset, yOffset, zOffset) { let trs = this.modelRoot.getComponent(xrFrameSystem.Transform); trs.setData({ visible: true }) XRClient.log("set digital root" + xOffset + " " + yOffset + " " + zOffset); trs.position.setValue(xOffset, yOffset, zOffset); } setCube() { const geometry = this.scene.assets.getAsset('geometry', 'cube');//以方块为例,也可替换为.glb格式的模型 const effect = this.scene.assets.getAsset('effect', 'standard'); const beOccMaterial = this.scene.createMaterial(effect); beOccMaterial.setVector('u_baseColorFactor', xrFrameSystem.Vector4.createFromNumber(1.0, 0, 0, 1.0)); this.modelRoot.getComponent(xrFrameSystem.Transform).setData({ visible: false }) for (let i = 0; i < this.digitalInfo.length; i++) { let item = this.digitalInfo[i]; const cube = this.scene.createElement(xrFrameSystem.XRNode); cube.addComponent(xrFrameSystem.Mesh, { geometry: geometry, material: beOccMaterial, }); this.modelRoot.addChild(cube); const trs = cube.getComponent(xrFrameSystem.Transform); trs.position.setValue(item.position.x, item.position.y, -item.position.z); XRClient.log("set " + i + " cube posi:" + trs.position.x + " " + trs.position.y + " " + trs.position.z); } } onVpsTrackingState(evt) { if (evt.data.status) { XRClient.log("show digital"); let modelRootTrs = this.modelRoot.getComponent(xrFrameSystem.Transform); modelRootTrs.setData({ visible: true }) } else { XRClient.log("hide digital"); let modelRootTrs = this.modelRoot.getComponent(xrFrameSystem.Transform); modelRootTrs.setData({ visible: false }) } } public destroy(){ XRClient.removeEvent(GlobalBus.VPS_TRACKING, this.onVpsTrackingState); } } export default ModelMag;
- 在index.js文件中实例化ModelMag。
import { XRClient } from "../../build/XRClient"; import { GlobalBus } from "../../utils/GlobalBus"; import modmag from "../../utils/ModelMag"; Component({ methods: { handleReady({ detail }) { this.scene = detail.value; this.offsetSetted = false; XRClient.log("handleReady"); }, handleARReady: function ({ }) { XRClient.log('arReady: arVersion ' + this.scene.ar.arVersion); XRClient.log('arReady: version ' + this.scene.ar.version); // 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; // 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 let config = { access: process.env.HUAWEICLOUD_SDK_AK, secret: process.env.HUAWEICLOUD_SDK_SK, beforeLocateDelta: 15, afterLocateDelta: 60, autoRequest: true, workerPath: 'build/workers/ImageProcessWorker.js', url:'' } XRClient.init(config); XRClient.dispatchEvent(GlobalBus.SCENE_INSTANCE, { info: this.scene }); XRClient.addEvent(GlobalBus.VPS_RESULT, this.onVpsResult, this); this.arInited = true; let mod = new modmag(this.scene); this.mod = mod; }, handleTick() { XRClient.updatePerFrame(); }, onVpsResult(evt) { XRClient.log("onVpsResult status" + evt.data.status); XRClient.log("onVpsResult info" + evt.data.info); if (evt.data.status) { XRClient.log("VPS success"); wx.showToast({ title: '定位成功', icon: 'success', duration: 2000 }) if (!this.offsetSetted) { this.offsetSetted = true; let arr = XRClient.getCameraOffset(); this.mod.setPosition(-arr[0], -arr[2], arr[1]); } } else { XRClient.log("VPS failure"); wx.showToast({ title: '定位失败', icon: 'none', duration: 2000 }) } }, }, lifetimes:{ attached(){ XRClient.log("attached"); }, detached(){ XRClient.log("detached"); if(this.arInited) { XRClient.destroyVps(); XRClient.removeEvent(GlobalBus.VPS_RESULT, this.onVpsResult); this.mod.destroy(); } } } })
- 单击“真机调试”,定位成功后,在空间中显示红色方块。
图4 显示红色方块
父主题: 开启AR会话