1.引言
你是否也有这样的时刻:打开手机准备背单词,却被弹出的通知带走了注意力,五分钟后又开始在刷短视频。
我们总想利用通勤和排队的碎片时间,但始终无法逃离手机这个时间黑洞,直到我开始佩戴Rokid AR眼镜,看着眼前空旷的视野,我突然有了一个想法。为何不开发一款极简背单词软件?

2.前期准备
基础环境使用Unity2022.3.62开发,IDE使用VS2022。SDK前期配置不过多赘述,可参考官网文档。
开发环境搭建文档
SDK导入
需注意,Unity安装时记得勾选Android Build Support 完整组件。
2.1 设置
打开Unity Project Settings,找到XR Plug-in Management,勾选OpenXR。找到OpenXR页签,加入Rokid HandTracking Profile、Rokid Controller Profile。在下方OpenXR Feature Groups处勾选Hand Tracking Subsystem、Rokid ARFoundation Support、Rokid Openxr Support。

在Package Manager中选择Rokid UXR SDK,加载Sample案例。(如果没有找到这个SDK,请看官方文档,如何导入Rokid package)

2.2 场景配置
找到场景uxr016-UIOverlay并复制一份,后序功能基于该场景开发。
删除不必要的层次结构
使用UGUI组装单词Label模板,添加布局组等,大致如下:
3.开发工作
3.1 前期分析
3.1.1 理清需求
在开发AR眼镜端的背单词应用时,我们面临几个关键需求:
简洁高效的UI展示:利用UGUI构建单词卡片,适配AR眼镜的显示特点
自动刷新机制:每隔一定时间自动更换显示的单词,保持内容新鲜度
随机抽取:从词库中随机选择单词,避免学习顺序固化
组件化设计:将显示逻辑和业务逻辑分离,便于维护和扩展
基于以上需求,我们设计了两个核心脚本:LabelItem(负责单个单词卡片的显示)和WordDisplayController(负责整体的刷新调度和单词管理)。
3.1.2 核心机制
数据层:硬编码30个常用单词,作为演示用词库
定时器:使用协程实现非阻塞的定时刷新,避免Update轮询
对象池思想:每次刷新时销毁旧实例、创建新实例(可根据性能优化需求改为对象池)
随机算法:Fisher-Yates洗牌算法确保每次抽取的单词足够随机
3.1.3 关键技术点
UGUI动态实例化:通过Instantiate创建预制体,并设置父物体为布局组
组件通信:控制器通过GetComponent获取LabelItem并调用其SetWord方法
协程控制:使用StartCoroutine和WaitForSeconds实现间隔执行
泛型洗牌算法:可复用的List洗牌方法,保证随机性
3.2 实践
3.2.1 层级修改
基于上一部分的层级结构,先进行一些修改.将模板节点放置于布局节点外部,并设为非激活状态,便于后序实例化。

3.2.2 LabelItem代码编写
编写LabelItem脚本代码,并将该脚本挂载于Template上,设置wordText引用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
/// <summary>
/// 挂在单词预制体(Template)上的脚本,用于接收并显示单词文本
/// </summary>
public class LabelItem : MonoBehaviour
{
// 在Inspector中需将预制体上的Text组件拖拽绑定到此字段
[SerializeField] private Text wordText;
/// <summary>
/// 由控制器调用,设置显示的单词
/// </summary>
/// <param name="word">要显示的单词字符串</param>
public void SetWord(string word)
{
if (wordText != null)
wordText.text = word;
else
Debug.LogWarning("LabelItem: wordText 未赋值,请检查预制体绑定。");
}
}
3.2.3 WordDisplayController
编写控制器代码脚本,该脚本挂载于MainPanel处
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// 背单词主控制器:管理30个单词库,每隔10秒随机刷新4个单词到布局组下
/// </summary>
public class WordDisplayController : MonoBehaviour
{
[Header("UI 引用")]
[Tooltip("存放单词预制体的父物体,需挂载水平或网格布局组件")]
[SerializeField] private Transform container; // 布局组所在的父物体(如Content)
[Tooltip("单词预制体(Template),需挂载 LabelItem 脚本")]
[SerializeField] private GameObject templatePrefab; // 带有LabelItem的预制体
[Header("配置参数")]
[Tooltip("每次显示的单词数量")]
[SerializeField] private int wordsPerBatch = 4;
[Tooltip("刷新间隔(秒)")]
[SerializeField] private float refreshInterval = 10f;
// 单词库(硬编码30个常用单词)
private readonly List<string> wordDatabase = new List<string>()
{
"apple", "banana", "cherry", "dog", "cat",
"house", "car", "tree", "book", "pen",
"sun", "moon", "star", "cloud", "rain",
"happy", "sad", "big", "small", "hot",
"cold", "run", "jump", "eat", "drink",
"red", "blue", "green", "yellow", "black"
};
// 用于暂存当前显示的Item实例(方便后续清除)
private List<GameObject> currentItems = new List<GameObject>();
private void Start()
{
// 启动循环刷新协程
StartCoroutine(RefreshLoop());
}
// 无限循环:每次清空旧项 -> 生成新批单词 -> 等待指定时间
private IEnumerator RefreshLoop()
{
while (true) // 无限循环,适合AR眼镜长时间运行
{
ClearCurrentItems();
SpawnRandomWords(wordsPerBatch);
yield return new WaitForSeconds(refreshInterval);
}
}
/// <summary>
/// 从数据库中随机选取指定数量的单词并实例化显示
/// </summary>
/// <param name="count">本次要显示的单词数量</param>
private void SpawnRandomWords(int count)
{
if (templatePrefab == null)
{
Debug.LogError("templatePrefab 未赋值,无法实例化单词项。");
return;
}
if (container == null)
{
Debug.LogError("container 未赋值,无法设置父物体。");
return;
}
// 随机打乱数据库,取前count个(如果count大于数据库总数,则取全部)
List<string> shuffled = new List<string>(wordDatabase);
Shuffle(shuffled);
int takeCount = Mathf.Min(count, shuffled.Count);
for (int i = 0; i < takeCount; i++)
{
// 实例化预制体,并设置父物体为container
GameObject newItem = Instantiate(templatePrefab, container);
newItem.SetActive(true);
// 获取LabelItem组件并赋值单词
LabelItem label = newItem.GetComponent<LabelItem>();
if (label != null)
{
label.SetWord(shuffled[i]);
}
else
{
Debug.LogWarning("预制体上缺少 LabelItem 组件,无法设置文字。");
}
// 记录已生成的Item,便于后续清除
currentItems.Add(newItem);
}
}
/// <summary>
/// 销毁所有当前显示的单词项
/// </summary>
private void ClearCurrentItems()
{
foreach (GameObject item in currentItems)
{
if (item != null)
Destroy(item);
}
currentItems.Clear();
}
/// <summary>
/// 对List进行随机洗牌(Fisher–Yates算法)
/// </summary>
private void Shuffle<T>(List<T> list)
{
int n = list.Count;
for (int i = n - 1; i > 0; i--)
{
int j = Random.Range(0, i + 1);
T temp = list[i];
list[i] = list[j];
list[j] = temp;
}
}
}
最终运行后,即可看见单词不断刷新了。
4.总结
这次开发实践验证了AR眼镜在“微学习”领域的巨大潜力。当手机等传统设备不断用通知和丰富的应用争夺我们注意力时,AR眼镜凭借其“始终在场,却又隐身于视野边缘”的特性,为我们开辟了一个新的专注地带。通过简单的技术组合,我们就能将那些流失在App切换间的碎片时间,重新转化为有价值的输入。
希望本文的思路与代码能激发更多开发者的灵感,一同探索AR与教育、效率工具结合的无限可能,让科技真正帮助我们夺回被“黑洞”吞噬的时间。戴上眼镜,世界就是你的学习界面。