0. 项目背景
大二时候立项的校级大学生创新项目,当时受到 Meta 吹嘘的元宇宙影响想到了这么一个项目,总结下来就是给基于 AR 技术给学校的党史长廊做一个导游的 APP 。
项目地址:https://github.com/fengwm64/ARGallerySmartGuide
(暂未开源,2025.09后开源)
1. 项目结构
基于 Unity 平台,使用 AR Foundation 框架进行开发。项目分为 3 个模块,采用一个模块对应一个场景的方法进行开发。模块分别为 导览页、AR扫描页、AR导航页,对应项目中的 Assets/Scenes/
下的:
Assets/Scenes/Home.unity
导览页Assets/Scenes/ARScan.unity
AR扫描页Assets/Scenes/ARNav.unity
AR导航页
2. UI设计
2.1 场景切换
项目有三个场景,通过UI中的TAB栏进行切换。
所谓TAB栏其实就是一个有三个按钮组成的容器。结构如下:
1 | 程序UI界面 |
首先,在 底部TAB栏
挂载一个脚本 Assets/Scripts/UI/TabController.cs
,脚本会检测用户点击了第几个按钮,然后加载对应的场景内容,完成场景切换。
其次,需要有一个实体记录当前位于的场景,在TAB栏高亮对应的按钮。建立脚本 Assets/Scripts/UI/TabManager.cs
,然后建立一个空的游戏对象挂载此脚本,保存为预制体 Assets/Prefabs/TabManager.prefab
,在 底部TAB栏
的 TabController
中引用预制体。
2.2 TAB栏自适应
现在的手机都是全面屏设计,分辨率也是五花八门的,为了可以使得 TAB 栏与屏幕等宽并且位于屏幕最下方且不会遮挡全面屏控制的“小白条”,编写一个脚本 Assets/Scripts/UI/TabBarWidthAdjuster.cs
自动布局 TAB 栏到安全区域内,同样挂载到 底部TAB栏
对象上。
3. 导览页设计
3.1 场景结构
导览页场景主要由 搜索栏、滚动区域两部分组成。其中滚动区域是一个无限滚动列表,用于展示各个景点。详细结构如下:
1 | Home |
3.2 数据库设计
为了存储各个景点的详细信息,利用 SQLite 设计数据库如下:
- Descriptions 表
字段名 | 数据类型 | 长度 | 非空 | 唯一 | 主键 | 自增 | 备注 |
---|---|---|---|---|---|---|---|
DescriptionID | INTEGER | - | 是 | 是 | 是 | 是 | 描述ID(主键,自增) |
SceneID | INTEGER | - | 是 | 否 | 否 | 否 | 关联的场景ID |
DetailedInfo | TEXT | 1500 | 是 | 否 | 否 | 否 | 详细描述信息 |
- Scenes 表
字段名 | 数据类型 | 长度 | 非空 | 唯一 | 主键 | 自增 | 备注 |
---|---|---|---|---|---|---|---|
SceneID | INTEGER | - | 是 | 是 | 是 | 是 | 场景ID(主键,自增) |
SceneName | TEXT | 255 | 是 | 否 | 否 | 否 | 场景名称 |
ScenePhoto | TEXT | 255 | 否 | 否 | 否 | 否 | 场景照片URL |
SceneThumbnail | TEXT | 255 | 否 | 否 | 否 | 否 | 场景缩略图URL |
DescriptionID | INTEGER | - | 否 | 否 | 否 | 否 | 关联的描述ID |
数据库文件放置在 Assets/StreamingAssets/ScenesData.db
中,因为移动端Android和iOS都不允许直接访问 StreamingAssets
文件夹中的文件,故在 首页
组件挂载脚本 Assets/Scripts/Database/DbInit.cs
,该脚本功能是将启动时候将 StreamingAssets
下的数据库文件拷贝到APP下的持久化访问路径。
数据库相关的动态链接文件为 libsqlite3.so
和 Mono.Data.Sqlite.dll
,具体放置方法如下:
1 | Assets/Plugins |
数据库增删改查的所有操作封装在脚本 Assets/Scripts/Database/DbAccess.cs
中。
3.3 无限滚动列表设计
无限滚动列表的原理其实就是,列表向上滑动时,当
item
超过显示区域的上边界时,把item
移动到列表底部,重复使用item
并更新item
的ui显示,向下滑动同理,把超过显示区域底部的item
复用到顶部。
这部分我是参考【游戏开发实战】Unity UGUI实现循环复用列表,显示巨量列表信息,含Demo工程源码,在此感谢@林新发。
项目中,涉及无限循环列表的组件和脚本如下:
滚动视图
挂载Assets/Scripts/List/RecyclingListViewItem.cs
ChildItem
挂载Assets/Scripts/List/ListItem.cs
主页
挂载Assets/Scripts/List/CreateList.cs
启动时候查询数据库,然后创建滚动列表
3.4 搜索功能实现
设计时候考虑到列表中的景点会很多,设计了一个搜索栏,由输入框和输入按钮组成。
在搜索按钮上挂载脚本 Assets/Scripts/List/SearchList.cs
。
在 SearchList.cs
脚本中,字符串相似度计算使用的是 Levenshtein 距离算法。该算法通过计算两个字符串之间的编辑距离来确定它们的相似度。编辑距离是指将一个字符串转换为另一个字符串所需的最少编辑操作次数(插入、删除或替换一个字符)。
当搜索到结果后会自动滚动到与搜索关键词最相似的行进行黄色效果的高亮。

4. AR扫描页设计
4.1 场景结构
1 | ARScan |
4.2 引导动画
参考网络上的类似 APP 都有做引导的动画,我也做了一个。没有触发 AR 扫描效果时候显示引导动画,这里所谓动画其实一段视频,从谷歌 ARCore 的 Demo 里偷的。这部分的代码在 Assets/Scripts/Animation/PlayVideoOnStart.cs
中实现,其实就是做了个视频播放器,循环播放 Assets/Resources/video/hand_oem.webm
的引导的视频。
4.3 AR扫描效果管理器
一般来说直接都是直接将图片到ReferenceImageLibrary
,然后直接在XR Origin
的ARTrackedImageManager
脚本上挂载AR效果的预制体,但是这样做只能挂载一个预制体,存在难以管理不同AR效果预制体的问题。为了更好地管理不同的AR效果预制体,我们可以通过脚本动态管理AR效果的生成和销毁。
创建脚本Assets/Scripts/ARScan/ImageTracker.cs
统一管理AR扫描效果目标,挂载到XR Origin上
,支持3种目标,分别为AR 3D模型、AR视频、AR文字介绍。
4.4 AR扫描效果制作
4.4.1 收集识别图进行预处理
- 收集:拿手机去长廊上面拍照,尽量在光照良好的情况下正对展墙上的识别图进行拍照
- 抠图:去除背景,将识别图扣出来
- 矫正:利用图像处理工具对畸形的图像进行透视矫正
这里我用的是手机上的夸克扫描王工具,一条龙做完,导出时候使用导出为图片
不用充值会员。
4.4.2 扫描图导入Unity
图片导入到Assets/Resources/img/AR扫描识别图
内,导入后点击图片在右边检查器
窗口设置纹理类型
为Sprite(2D和UI)
,然后点击空白处等弹出窗口,然后点击保存
然后,添加图片到ReferenceImageLibrary
,位置Assets/ARScanImgLib/ReferenceImageLibrary.asset
。点击ReferenceImageLibrary.asset
后在右边检查器
窗口添加图片,注意Name
唯一;勾选Specify Size
,Physical Size
设置X
为0.2
,Y
会自动计算无需输入。
4.4.3 建立AR效果的预制体
AR效果都是在预制体里设计的,做好后放在Assets/Prefabs/AR扫描效果预制件
文件夹下,保证在预制体中做的所有效果放在PanelParent/Panel
下。
如一个3D模型的预制体结构如下:
1 | 第一辆汽车 |
第一辆汽车
的Transform
为位置(0,0,0)
,旋转(0,0,0)
,缩放(1,1,1)
PanelParent
的Transform
为位置(0,0,0)
,旋转(0,0,0)
,缩放(1,1,1)
Panel
的Transform
为位置(0,0,0)
,旋转(0,0,0)
,缩放(0.1,0.1,0.1)
Panel
的子对象就是设计AR效果内容,设置位置(0,0,0)
,旋转与缩放按实际需要填写(一开始填000和111就可以,不行再调整)
⚠️注意:AR视频直接使用Assets/Prefabs/AR扫描效果预制件/视频播放预制件.prefab
即可;AR文字不需要做预制体,代码自己生成。
4.4.4 挂载
在ARScan
场景左侧的层级
面板中点击XR Origin
,然后在右边的检查器
面板展开Image Tracker
脚本,然后在arTargets
中加一个对象,然后填对应ReferenceImageLibrary
图片的名字,挂载预制件,设置内容类型等等参数
- 注意AR视频才需要设置
videoPath
,AR文字才需要设置introductionText
、textColor
、textMargin
等参数
4.5 AR 3D模型交互
当扫描显示AR 3D模型效果后,用户自然会想到与模型进行一些交互,如旋转、放缩等。
创建一个脚本Assets/Scripts/ARInteraction/ARModelInteraction.cs
,挂载预制体的AR 3D模型上,具体来说挂载在Panel
的第一个子对象上,既可实现与模型的交互。
5. AR导航页设计
5.1 场景结构
1 | ARNav |
5.2 长廊地图建模
在 层级
面板中建立 AR导航地图
对象,然后建立导航网格 FirstFloor-NavigationArea
,使用长廊的介绍的小地图图片制作一个 FloorCubeMaterial
材质作为底图,使用 cube
将可以行走的区域围出来。
5.3 导航目标管理
创建一个 AR导航控制器
对象,挂载 Assets/Scripts/Core/TargetHandler.cs
,该脚本实现对AR导航目标的管理,AR导航的目标存储在一个JSON文件 Assets/Resources/TargetData.json
中,格式如下:
1 | { |
在JSON中写明目标在Unity中的位置和旋转的信息(旋转一般为(0,0,0)),然后在 检查器
面板的 TargetHandler
中引用这个JSON文件。
其次,在 TargetHandler
中,Start
方法中,调用了 GenerateTargetItems
和 FillDropdownWithTargetItems
方法,分别用于生成目标项和填充下拉菜单。GenerateTargetItems
方法从数据源读取目标数据,并通过调用 CreateTargetFacade
方法创建目标点的可视化对象。CreateTargetFacade
方法实例化目标对象预制体,并根据目标数据设置其位置、旋转和属性。
GenerateTargetDataFromSource
方法从 JSON 文件解析目标数据,并返回目标数据集合。FillDropdownWithTargetItems
方法将所有目标点信息转换为下拉菜单选项数据,并清空下拉菜单后添加新的选项。
此外,类中还包含两个公共方法:SetSelectedTargetPositionWithDropdown
和 GetCurrentTargetByTargetText
。SetSelectedTargetPositionWithDropdown
方法根据下拉菜单选择设置导航目标,GetCurrentTargetByTargetText
方法根据目标名称查找目标对象。GetCurrentlySelectedTarget
方法用于获取指定索引的目标位置,如果选择值超出范围,则返回零向量。
5.4 导航路线可视化
导航路线可视化部分由 Assets/Scripts/Utilities/PathVisualisation/
负责,主要为 PathLineVisualisation.cs
负责切换两种不同的导航路线可视化方法;PathArrowVisualisation.cs
负责实现箭头类型的可视化方法;PathLineVisualisation.cs
负责实现线段类型的可视化方法。
创建 AR导航可视化指示器
对象,分别对应两种可视化line
和GameObject
对象,挂载对应的脚本。
可视化部分PathArrowVisualisation
逻辑为:
首先从 navigationController
获取当前计算的导航路径,然后调用 AddOffsetToPath
方法为路径点添加高度偏移。接着调用 SelectNextNavigationPoint
方法选择下一个导航点,并调用 AddArrowOffset
方法根据滑块值调整箭头的 Y 轴位置。最后,将箭头
对象朝向下一个导航点。
PathLineVisualisation.cs
部分的逻辑与上面类似,不再赘述。
5.5 小地图设计
同样在 AR导航可视化指示器
对象下创建子对象,IndicatorSphere
标识用户目前处在的位置,小地图摄像机
对象(从上俯拍地图)。
在UI区域添加一个 MiniMapRawImage
用于显示摄像机画面。
5.6 利用二维码实现重定位
5.6.1 介绍
创建一个组件 二维码扫描
,挂载脚本 Assets/Scripts/Core/QrCodeRecenter.cs
,然后在UI中设置对应激活的按钮。
QrCodeRecenter.cs
用于在Unity中处理二维码扫描和场景重定位,实现了从相机帧中检测二维码并根据二维码内容重置AR会话的位置和旋转。
类中定义了一些序列化字段,这些字段可以在Unity Inspector中进行设置,包括 ARSession
、XROrigin
、ARCameraManager
、TargetHandler
和 GameObject
类型的 qrCodeScanningPanel
。这些字段分别用于管理AR会话的生命周期、重定位AR会话的原点、获取相机帧、管理目标对象以及显示或隐藏二维码扫描面板。
类中还定义了一些私有字段,包括用于存储相机帧纹理的 Texture2D
、用于创建二维码读取器实例的 IBarcodeReader
以及控制扫描启用状态的布尔变量 scanningEnabled
。
在 OnEnable
方法中,注册了相机帧事件,当组件启用时会调用 OnCameraFrameReceived
方法处理相机帧数据。相应地,在 OnDisable
方法中,注销了相机帧事件,以确保在组件禁用时不再处理相机帧数据。
OnCameraFrameReceived
方法是处理相机帧数据的核心部分。首先,它检查扫描是否启用,如果未启用则直接返回。然后尝试获取最新的CPU图像,如果获取失败也会返回。接下来,设置图像转换参数,包括输入矩形、输出尺寸、输出格式和转换方式。计算存储最终图像所需的字节数,并分配缓冲区来存储图像数据。提取图像数据后,将其转换为RGBA32格式并写入缓冲区,随后释放 XRCpuImage
以避免资源泄漏。将数据放入纹理中以便可视化,并在完成临时数据处理后释放缓冲区。最后,检测并解码纹理中的二维码,如果解码成功则调用 SetQrCodeRecenterTarget
方法设置重新定位目标,并调用 ToggleScanning
方法切换扫描状态。
SetQrCodeRecenterTarget
方法根据二维码内容设置场景重定位目标。它通过 targetHandler
查找对应目标,并重置AR会话的位置和旋转,将 sessionOrigin
的位置和旋转设置为目标对象的位置和旋转。
ChangeActiveFloor
方法用于切换到指定楼层,它调用 SetQrCodeRecenterTarget
方法并传入楼层入口标识符。
最后,ToggleScanning
方法用于切换二维码扫描状态,并根据扫描状态显示或隐藏扫描面板。
5.6.2 二维码制作
我使用的是草料二维码,二维码信息为文本类型,内容为对应的景点名字,如“主题雕塑:地球上的红飘带”。
示例二维码如下:

