马年新春AR祝福语:基于Rokid 的虚实融合拜年应用开发实战

0 阅读10分钟

引言

2025年是AI眼镜从“极客玩具”转向“实用工具”的关键之年,在激烈的“百镜大战”中,我们迎来了2026年马年春节。作为下一代移动计算平台,AI眼镜正在重塑我们与现实世界的交互方式。在这个阖家团圆的节日里,如何用技术为传统拜年增添一份新意?

本文将基于Rokid 和Unity引擎,开发一款“AR祝福语眼镜”应用。当佩戴者看向亲友时,空中会浮现金色的新年祝福文字,按下眼镜按键(模拟拥护实时对话)即可切换不同祝福语,配合深红色的喜庆背景,打造虚实交融的拜年体验。通过这个项目,你将掌握Rokid AR应用开发的核心流程:环境搭建、世界空间UI创建、按键交互、语音识别集成,以及完整的代码实现。所有代码均可在Unity编辑器中直接运行测试,最终打包部署到Rokid设备即可体验真机效果。

一、开发环境与项目配置

1.1 必要软件与工具

  • Unity 2022.3 :需包含Android Build Support模块

  • Rokid UXR3.0 SDK:通过Unity Package Manager导入

  • Visual Studio 2022:用于代码编辑

  • Rokid Glasses开发者模式:用于最终部署测试(本文重点为代码实现,真机部署步骤从略)

1.2 创建Unity项目

  1. 打开Unity Hub,新建3D项目,选择Unity 2022.3 LTS版本,项目命名为“ARBlessingGlasses”。

  2. 进入项目后,打开菜单栏 Window -> Package Manager,点击左上角“+”号,选择“Add package from git URL”,输入Rokid UXR SDK的Git地址(具体地址请参考Rokid官方文档),等待导入完成。

  3. 导入成功后,Project窗口会出现“Rokid”文件夹,其中包含预制体、示例场景和核心API。

1.3 场景基础设置

在Hierarchy窗口中,删除默认的Main Camera和Directional Light。从Rokid SDK的Prefabs文件夹中,将“AR Session Origin”和“AR Camera”拖入场景。AR Session Origin包含了相机跟踪和空间感知的基础组件,AR Camera则是负责渲染的相机。

为了在开发阶段方便预览,我们会在场景中添加一个测试用的相机控制器(CameraAutoMover),但最终真机运行时,相机的移动完全由设备自身的跟踪系统驱动,因此测试脚本仅用于编辑器调试。

二、核心功能实现

2.1 祝福语数据与交互逻辑

我们使用一个字符串数组存储多条祝福语,覆盖长辈、朋友、同学等不同对象。每条祝福语都包含传统的吉祥话,并加入马年元素。

private static readonly string[] Blessings = new string[]
{
    "祝您马年大吉,龙马精神,身体健康,万事如意!",
    "新春到,祝长辈们笑口常开,福如东海,马到成功!",
    "我的朋友,新年快乐!愿我们的友谊地久天长,马年行大运!",
    "祝同学们马年学业进步,一马当先,金榜题名!",
    "新年快乐,阖家幸福,马年吉祥,心想事成!",
};

切换祝福语的逻辑非常简单:按下指定按键时,将数组中当前索引对应的文字赋值给Text组件,然后索引自增,并通过取模运算实现循环。

if (Input.GetKeyDown(KeyCode.X) && _blessingCanvas != null && _blessingText != null)
{
    _blessingText.text = Blessings[_blessingIndex];
    _blessingIndex = (_blessingIndex + 1) % Blessings.Length;
    _blessingCanvas.SetActive(true);
}

在真机上,我们不会使用键盘X键,而是通过Rokid设备的按键事件来触发。为了方便开发和测试,我们保留键盘输入,同时在代码中预留Rokid按键的接入点。

2.2 世界空间UI的创建

为了让祝福语在现实世界中悬浮,我们必须使用World Space模式的Canvas。以下是完整的UI创建方法:

void CreateBlessingUI()
{
    // 创建Canvas根对象
    GameObject root = new GameObject("BlessingCanvas");
    root.transform.position = new Vector3(0f, 1.5f, -5f);  // 位于相机前方
    root.transform.rotation = Quaternion.identity;
    root.transform.localScale = Vector3.one;

    // 添加Canvas组件并设置为世界空间
    Canvas canvas = root.AddComponent<Canvas>();
    canvas.renderMode = RenderMode.WorldSpace;
    canvas.worldCamera = Camera.main;  // 指定渲染相机

    // 设置RectTransform的尺寸和缩放
    RectTransform rootRect = root.GetComponent<RectTransform>();
    rootRect.sizeDelta = new Vector2(520, 140);
    rootRect.localScale = new Vector3(0.006f, 0.006f, 0.006f);  // 缩放至合适大小

    root.AddComponent<CanvasScaler>();
    root.AddComponent<GraphicRaycaster>();

    // 创建文本子对象
    GameObject textObj = new GameObject("BlessingText");
    textObj.transform.SetParent(root.transform);

    Text blessingText = textObj.AddComponent<Text>();
    blessingText.text = Blessings.Length > 0 ? Blessings[0] : "";
    blessingText.fontSize = 22;
    blessingText.color = new Color(1f, 0.85f, 0.2f);  // 金黄色
    blessingText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
    blessingText.alignment = TextAnchor.MiddleCenter;
    blessingText.horizontalOverflow = HorizontalWrapMode.Wrap;
    blessingText.verticalOverflow = VerticalWrapMode.Overflow;

    // 设置文本RectTransform居中并填满父对象
    RectTransform textRect = textObj.GetComponent<RectTransform>();
    textRect.anchorMin = new Vector2(0.5f, 0.5f);
    textRect.anchorMax = new Vector2(0.5f, 0.5f);
    textRect.pivot = new Vector2(0.5f, 0.5f);
    textRect.anchoredPosition = Vector2.zero;
    textRect.sizeDelta = new Vector2(520, 140);

    // 初始隐藏面板,等待触发显示
    root.SetActive(false);
    _blessingCanvas = root;
    _blessingText = blessingText;
}

关键点解析:

  • Canvas渲染模式:必须设置为WorldSpace,UI才会在三维空间中拥有实际位置和透视。
  • 缩放因子:sizeDelta定义UI元素的像素参考尺寸,localScale将其缩放到真实世界中的合适大小。这里0.006是经验值,在距离相机5米左右时,文字大小约13厘米,阅读舒适。
  • 字体与颜色:使用Unity内置字体确保兼容性,金黄色在深红背景下极为醒目。

2.3 相机与背景设置

为了让场景更具节日氛围,我们将相机背景色设为深红色,并设置初始位置和角度。注意:在真机上,相机位置由设备跟踪,这里的初始化只对编辑器测试有效。

void SetupARScene()
{
    Camera mainCamera = Camera.main;
    if (mainCamera != null)
    {
        mainCamera.transform.position = new Vector3(0, 1.5f, -8);
        mainCamera.transform.rotation = Quaternion.Euler(10, 0, 0);
        mainCamera.orthographic = false;
        mainCamera.fieldOfView = 60;
        mainCamera.backgroundColor = new Color(0.45f, 0.12f, 0.12f);  // 深红色
        mainCamera.clearFlags = CameraClearFlags.SolidColor;
    }
    CreateBlessingUI();
}

2.4 测试用相机移动控制器

为了在Unity编辑器中模拟AR眼镜中的自由视角,我们编写了一个简单的相机移动脚本。该脚本支持WASD平面移动、QE上下移动,以及鼠标右键拖拽旋转视角。在真机运行时,此脚本应当禁用,因为设备的SLAM会自然驱动相机。

using UnityEngine;

public class CameraAutoMover : MonoBehaviour
{
    [Header("鼠标控制设置")]
    public bool mouseControl = true;
    public float mouseSensitivity = 2f;
    public float moveSpeed = 5f;
    public float smoothTime = 0.1f;

    private Vector2 currentRotation;
    private Vector2 targetRotation;
    private Vector3 moveDirection;

    void Start()
    {
        Vector3 euler = transform.eulerAngles;
        currentRotation = new Vector2(euler.x, euler.y);
        targetRotation = currentRotation;
    }

    void Update()
    {
        if (!mouseControl) return;

        HandleMouseLook();
        HandleMovement();
    }

    void HandleMouseLook()
    {
        if (Input.GetMouseButton(1))
        {
            float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
            float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;

            targetRotation.y += mouseX;
            targetRotation.x -= mouseY;
            targetRotation.x = Mathf.Clamp(targetRotation.x, -80f, 80f);
        }

        currentRotation = Vector2.Lerp(currentRotation, targetRotation, smoothTime * 10f * Time.deltaTime);
        transform.rotation = Quaternion.Euler(currentRotation.x, currentRotation.y, 0);
    }

    void HandleMovement()
    {
        float horizontal = 0f, vertical = 0f;
        if (Input.GetKey(KeyCode.W)) vertical = 1f;
        if (Input.GetKey(KeyCode.S)) vertical = -1f;
        if (Input.GetKey(KeyCode.A)) horizontal = -1f;
        if (Input.GetKey(KeyCode.D)) horizontal = 1f;

        float height = 0f;
        if (Input.GetKey(KeyCode.E)) height = 1f;
        if (Input.GetKey(KeyCode.Q)) height = -1f;

        Vector3 forward = transform.forward;
        Vector3 right = transform.right;
        moveDirection = forward * vertical + right * horizontal + Vector3.up * height;
        moveDirection.Normalize();

        transform.position += moveDirection * moveSpeed * Time.deltaTime;
    }
}

三、集成Rokid交互

3.1 按键事件处理

Rokid Glasses提供了多个交互按键,如功能键、返回键等。在Rokid UXR SDK中,可以通过Input系统获取按键事件。我们需要在GameSetup脚本中添加对Rokid按键的监听,替代键盘X键。

首先,在脚本顶部引入Rokid命名空间:

using Rokid.UXR;

然后在Update方法中,除了检查键盘输入,还检查Rokid按键:

void Update()
{
    // 键盘X键用于编辑器测试
    if (Input.GetKeyDown(KeyCode.X) && _blessingCanvas != null && _blessingText != null)
    {
        SwitchBlessing();
    }

    // Rokid功能键短按切换祝福语
    if (RokidInput.GetButtonDown("PrimaryButton"))
    {
        SwitchBlessing();
    }
}

void SwitchBlessing()
{
    _blessingText.text = Blessings[_blessingIndex];
    _blessingIndex = (_blessingIndex + 1) % Blessings.Length;
    _blessingCanvas.SetActive(true);
}

RokidInput类是SDK提供的输入封装,具体按键名称可参考SDK文档。

3.2 语音识别集成

Rokid设备支持离线语音识别,我们可以让用户说出“切换祝福”或“下一条”来切换祝福语。语音识别需要在场景中配置语音识别组件。

  1. 从Rokid SDK的Prefabs中拖入“SpeechRecognizer”预制体到场景。

  2. 在GameSetup脚本中获取SpeechRecognizer组件,并注册识别结果回调。

using Rokid.Speech;

public class GameSetup : MonoBehaviour
{
    private SpeechRecognizer _speechRecognizer;

    void Start()
    {
        SetupARScene();
        InitSpeechRecognition();
    }

    void InitSpeechRecognition()
    {
        _speechRecognizer = FindObjectOfType<SpeechRecognizer>();
        if (_speechRecognizer != null)
        {
            _speechRecognizer.OnResult += OnSpeechResult;
            _speechRecognizer.StartListening();
        }
    }

    void OnSpeechResult(string result)
    {
        if (result.Contains("切换祝福") || result.Contains("下一条"))
        {
            SwitchBlessing();
        }
    }
}

注意:语音识别需要设备支持,在Unity编辑器中无法测试,需部署到真机验证。但代码可以先行写好,通过条件编译避免编辑器错误。

3.3 场景管理优化

为了确保祝福语面板始终面向相机(即始终正面显示),我们可以添加一个简单的Billboard脚本,让面板始终旋转对准相机。但考虑到AR体验中,文字悬浮在固定位置更符合直觉,所以这里不添加自动旋转,而是保持面板在世界空间中的固定姿态。这样当用户围绕面板走动时,文字会呈现侧面视角,产生真实的立体感。

四、完整代码汇总

GameSetup.cs

using UnityEngine;
using UnityEngine.UI;
using Rokid.UXR;
using Rokid.Speech;

public class GameSetup : MonoBehaviour
{
    private static readonly string[] Blessings = new string[]
    {
        "祝您马年大吉,龙马精神,身体健康,万事如意!",
        "新春到,祝长辈们笑口常开,福如东海,马到成功!",
        "我的朋友,新年快乐!愿我们的友谊地久天长,马年行大运!",
        "祝同学们马年学业进步,一马当先,金榜题名!",
        "新年快乐,阖家幸福,马年吉祥,心想事成!",
    };

    private GameObject _blessingCanvas;
    private Text _blessingText;
    private int _blessingIndex = 0;
    private SpeechRecognizer _speechRecognizer;

    void Start()
    {
        SetupARScene();
        InitSpeechRecognition();
    }

    void SetupARScene()
    {
        Camera mainCamera = Camera.main;
        if (mainCamera != null)
        {
            mainCamera.transform.position = new Vector3(0, 1.5f, -8);
            mainCamera.transform.rotation = Quaternion.Euler(10, 0, 0);
            mainCamera.fieldOfView = 60;
            mainCamera.backgroundColor = new Color(0.45f, 0.12f, 0.12f);
            mainCamera.clearFlags = CameraClearFlags.SolidColor;
        }
        CreateBlessingUI();
    }

    void InitSpeechRecognition()
    {
        _speechRecognizer = FindObjectOfType<SpeechRecognizer>();
        if (_speechRecognizer != null)
        {
            _speechRecognizer.OnResult += OnSpeechResult;
            _speechRecognizer.StartListening();
        }
    }

    void OnSpeechResult(string result)
    {
        if (result.Contains("切换祝福") || result.Contains("下一条"))
        {
            SwitchBlessing();
        }
    }

    void Update()
    {
        // 编辑器测试:按下X键切换
        if (Input.GetKeyDown(KeyCode.X) && _blessingCanvas != null && _blessingText != null)
        {
            SwitchBlessing();
        }

        // 真机:Rokid功能键短按切换
        if (RokidInput.GetButtonDown("PrimaryButton"))
        {
            SwitchBlessing();
        }
    }

    void SwitchBlessing()
    {
        _blessingText.text = Blessings[_blessingIndex];
        _blessingIndex = (_blessingIndex + 1) % Blessings.Length;
        _blessingCanvas.SetActive(true);
    }

    void CreateBlessingUI()
    {
        GameObject root = new GameObject("BlessingCanvas");
        root.transform.position = new Vector3(0f, 1.5f, -5f);
        root.transform.rotation = Quaternion.identity;
        root.transform.localScale = Vector3.one;

        Canvas canvas = root.AddComponent<Canvas>();
        canvas.renderMode = RenderMode.WorldSpace;
        canvas.worldCamera = Camera.main;

        RectTransform rootRect = root.GetComponent<RectTransform>();
        rootRect.sizeDelta = new Vector2(520, 140);
        rootRect.localScale = new Vector3(0.006f, 0.006f, 0.006f);

        root.AddComponent<CanvasScaler>();
        root.AddComponent<GraphicRaycaster>();

        GameObject textObj = new GameObject("BlessingText");
        textObj.transform.SetParent(root.transform);

        Text blessingText = textObj.AddComponent<Text>();
        blessingText.text = Blessings.Length > 0 ? Blessings[0] : "";
        blessingText.fontSize = 22;
        blessingText.color = new Color(1f, 0.85f, 0.2f);
        blessingText.font = Resources.GetBuiltinResource<Font>("LegacyRuntime.ttf");
        blessingText.alignment = TextAnchor.MiddleCenter;
        blessingText.horizontalOverflow = HorizontalWrapMode.Wrap;
        blessingText.verticalOverflow = VerticalWrapMode.Overflow;

        RectTransform textRect = textObj.GetComponent<RectTransform>();
        textRect.anchorMin = new Vector2(0.5f, 0.5f);
        textRect.anchorMax = new Vector2(0.5f, 0.5f);
        textRect.pivot = new Vector2(0.5f, 0.5f);
        textRect.anchoredPosition = Vector2.zero;
        textRect.sizeDelta = new Vector2(520, 140);

        root.SetActive(false);
        _blessingCanvas = root;
        _blessingText = blessingText;
    }
}

CameraAutoMover.cs

using UnityEngine;

public class CameraAutoMover : MonoBehaviour
{
    [Header("鼠标控制设置")]
    public bool mouseControl = true;
    public float mouseSensitivity = 2f;
    public float moveSpeed = 5f;
    public float smoothTime = 0.1f;

    private Vector2 currentRotation;
    private Vector2 targetRotation;
    private Vector3 moveDirection;

    void Start()
    {
        Vector3 euler = transform.eulerAngles;
        currentRotation = new Vector2(euler.x, euler.y);
        targetRotation = currentRotation;
    }

    void Update()
    {
        if (!mouseControl) return;

        HandleMouseLook();
        HandleMovement();
    }

    void HandleMouseLook()
    {
        if (Input.GetMouseButton(1))
        {
            float mouseX = Input.GetAxis("Mouse X") * mouseSensitivity;
            float mouseY = Input.GetAxis("Mouse Y") * mouseSensitivity;

            targetRotation.y += mouseX;
            targetRotation.x -= mouseY;
            targetRotation.x = Mathf.Clamp(targetRotation.x, -80f, 80f);
        }

        currentRotation = Vector2.Lerp(currentRotation, targetRotation, smoothTime * 10f * Time.deltaTime);
        transform.rotation = Quaternion.Euler(currentRotation.x, currentRotation.y, 0);
    }

    void HandleMovement()
    {
        float horizontal = 0f, vertical = 0f;
        if (Input.GetKey(KeyCode.W)) vertical = 1f;
        if (Input.GetKey(KeyCode.S)) vertical = -1f;
        if (Input.GetKey(KeyCode.A)) horizontal = -1f;
        if (Input.GetKey(KeyCode.D)) horizontal = 1f;

        float height = 0f;
        if (Input.GetKey(KeyCode.E)) height = 1f;
        if (Input.GetKey(KeyCode.Q)) height = -1f;

        Vector3 forward = transform.forward;
        Vector3 right = transform.right;
        moveDirection = forward * vertical + right * horizontal + Vector3.up * height;
        moveDirection.Normalize();

        transform.position += moveDirection * moveSpeed * Time.deltaTime;
    }
}

五、运行与测试

5.1 在Unity编辑器中测试

  1. 将GameSetup脚本挂载到场景中的空物体上。
  2. 从Rokid SDK的Prefabs中拖入AR Session Origin、AR Camera和SpeechRecognizer预制体。
  3. 如果需要模拟自由移动,可以将CameraAutoMover脚本挂载到AR Camera上(真机部署前需禁用此脚本)。
  4. 点击运行按钮,即可看到深红色背景,按X键可显示/切换祝福语,使用WASD和鼠标右键可自由移动视角观察悬浮的文字。

5.2 打包到Rokid设备

  1. 打开Build Settings窗口,将当前场景添加到Scenes In Build列表。
  2. 将Platform切换到Android,点击Switch Platform。
  3. 在Player Settings中配置包名、版本号等,确保Minimum API Level符合Rokid设备要求。
  4. 连接Rokid设备并开启开发者模式,点击Build And Run,Unity会自动编译并安装应用到设备。
  5. 在设备上启动应用,即可体验真实世界中的悬浮祝福语,通过设备功能键或语音切换。

六、技术要点总结

模块技术实现
祝福语显示世界空间Canvas,调整缩放因子控制实际大小
数据管理静态字符串数组,索引取模实现循环
交互触发键盘X(测试)、Rokid按键、语音识别三种方式
相机控制编辑器使用CameraAutoMover,真机由SLAM驱动
视觉风格深红背景、金黄色文字,营造春节氛围

七、扩展与优化建议

  1. 动态背景:可以使用粒子系统模拟飘落的雪花或烟花,增强节日气氛。
  2. 个性化祝福:通过接入Rokid灵珠AI平台,根据用户画像生成定制祝福语。
  3. 多人共享:利用Rokid的云锚点服务,让多位用户看到同一位置的祝福语。
  4. 手势交互:结合手势识别,用户可以通过手指点击或滑动来切换祝福语。
  5. 图像识别触发:在春节对联、福字上触发特定祝福语,增加寻宝乐趣。

结语

本文通过一个完整的AR祝福语眼镜应用开发,展示了基于Rokid Glasses和Unity引擎的AR应用开发流程。从环境搭建、UI创建、交互集成到代码实现,每一步都经过详细讲解。希望这个项目能激发你的创意,在即将到来的马年春节,用技术为传统拜年增添一份新意。赶快动手试试吧,期待看到你的独特作品!