jjzjj

Unity Pico Neo3 基础开发流程

Maddie_Mo 2023-04-04 原文

Unity Pico Neo3 基础开发流程

Pico 基础模块

Pico 开发者平台

链接: PICO 开发者平台
链接: PICO 文档中心
链接: Pico GitHub
链接: PicoXR SDK 官方存储库

如果是第一次进入需要先注册成为开发者。
然后下载SDK

这个是 Unity相关的 SDK
注意下载的平台,并下载最新版。

我这里下载的是:PICO UnityXR Integration SDK v207。

解压: 最有用的就是这个 package.json 文件。

这个是 实时预览 和 Pico Neo3 串流工具。

解压之后的文件夹状态:
再把 PreviewTool_0402_Release.7z 解压。
PreviewTool_0402.apk : 这个文件是 Pico Neo3 的安装包

在用 USB 数据线 连接到 PicoNeo3 把PreviewTool_0402.apk 文件复制到内部存储器中。
打开 PicoNeo3 并安装

下载 Android Debug Bridge(ADB) 调试工具包。

链接: Android Debug Bridge(ADB)

如果无法下载就尝试一下 下面这个链接。

链接: Android Debug Bridge(ADB) CSDN

Pico 管理中心

在 Pico 开发者平台 点击管理中心

进行账号密码登录,没有的话就注册一个

登录之后 点击创建一个新的应用

创建应用时最好 选择 6DOF

创建完毕之后,点击刚刚创建好的应用。

点击 当前应用 API

这里的 API 就是后期发布应用时会用到的。

Pico 实时预览 测试

1. 使用 USB 数据线连接 PICO VR 一体机与 PC
2. 使用 Win + R 输入 cmd 打开 命令行工具  

3. 在命令行窗口中 输出:cd D:\Unity\Plug-in\Pico SDK\platform-tools_r33.0.3-windows\Android Debug Bridge(ADB再次输入:d:

4. 状态检查 输入:adb devices。
  (若第一次执行该命令,此时 PICO VR 一体机屏幕上会通过对话框提示 “是否允许USB Debugger”,点击 同意。)
  (命令执行后,如果出现设备序列号,代表连接成功。)
  (这个序列号等会要在 Unity 中使用。)

5. 在PicoNeo3 中点击文件管理->安装包->PreviewTool_0402.apk 进行PreviewTool安装。

6.***\PicoPreviewTool V1.0-0402\PreviewTool_0402_Release\Release 文件夹下 双击打开 PreviewTool.exe

7. 两种链接模式:
   无线连接 :需保证 PCVR 一体机处于同一 Wi-Fi 环境下。
   (推荐) 有线连接 :需使用 USB 数据线连接 PCVR 一体机。

链接成功状态。

8. 在PicoNeo3打开  PreviewTool 工具

9. 选择有线连接 连接完毕之后,整个画面就会变黑。
   点击 Unity 编辑器界面顶部的 播放 按钮。
   VR 一体机上就会呈现与 PC 同步的场景画面。

Unity 模块

链接: 配置开发环境 Pico 官方文档

项目创建

1. Unity 编辑器(Unity Editor)须使用 2019.4.0 及以上版本。(因为要使用 XR 模块)

2. 先进行安卓平台 切换。

切换后的状态。

3. 打开 Package Manager。

4. 点击 Add package from disk

5. 选择 package.json 文件并导入。

6. 下载 XR Interaction Toolkit 并下载 Starter Assets、XR Device Simulator、Tunneling Vignette。

7. 在 App ID 字段处,填入应用 ID。点击 Apply。

8. 在安卓平台 勾选 PicoXR

9. 在微软平台 勾选 PicoXR

10. Other Settings 标签,在 Identification 设置区域:
   Minimum API Level (最低API级别)更改为 API level 27。
   Target API Level 设置为 Automatic (highest installed)

11. 在 Configuration 设置区域:
    Scripting Backend 设置为 IL2CPP。
    Target Architectures 设置为 ARM64 ,并取消勾选 ARMv7。

12. 在菜单栏 点击 PXR_SDK -> Platform Setting -> Authorization Check Simulation(授权检查模拟)

13. 添加设备 SN 码 就是在命令行工具中 获取到的那个 机器码

基础XR 模块

1. 在 Hierarchy窗口 右键 -> XR -> XR origin

2. 添加组件:
   XR_Origin:原点
   PXR_Manager:PXR 管理
   TeleportationProvider:传送
   LocomotionSystem:运动系统
   InputActionManager:输入操作
   DeviceBasedSnapTurnProvider:基于设备的快速转向   

3. 在 Hierarchy 窗口选中 LeftHand Controller\RightHand Controller
   剔除XRController(Action-based)组件。

4. 添加 XRController(Device-based)组件。
   RightHand Controller 也一样。

5. 注意在 XRController(Device-based)组件上添加 Model 模型,不然就会只有射线。
   RightHand Controller 也一样。
   Model 模型在:Packages/Pico intergration/Assets/Resources/Prefabs 文件夹下

6. XR Interactor Line Visual:射线风格化
   更改 Width Curve 风格化:下行线 的效果是 手柄宽,射线终点窄。
   Reticle:射线终点,如果不设置就是单纯的射线,如果设置就是你设置的模型。
   我这里是使用的 Sphere 小球,大小 0.01f。

传送 模块

在 Hierarchy 窗口 创建一个 Plane 把Transform组件 Reset一下。
并添加 TeleportationArea:传送区域 组件。

锚点传送 模块

在 Hierarchy 窗口 创建一个 Plane 更改名字(改不改都行)
再在此物体下创建一个空物体作为锚点位置(那个蓝色的小框)。
在 Plane 上添加 TeleportationAnchor:锚点传送 组件
并把锚点位置 空物体赋予给 Telepor Anchor Transform(锚点物体)

射线抓取 模块

在 Hierarchy 窗口 创建一个 Sphere 更改名字(改不改都行)
在 Sphere 上添加 XR Grb Interactable:抓取 组件

手柄碰撞抓取 模块

和上面的差不多
在 Hierarchy 窗口 创建一个 Cube 更改名字(改不改都行)
在 Cube上添加 XR Grb Interactable:抓取 组件。
区别就是 注意添加手柄碰撞抓取。

当然手柄也要做出响应的变化:
把你需要响应的手只保留 XR Controller(Device-based)组件。
并添加 XR Direct InterActor 和 Capsule Collider 组件。
Capsule Collider 组件的 Radius 和 Height 设置为 0.1f。

XR UI模块

在Hierarchy 窗口 右键 XR -> UI Canvas 进行 UI 模块创建。

为什么要在 XR 的模块下创建 UI 呢? 他和普通的 UI 差别在哪里呢?
首先使用 常规 UI 创建画布 那么 XR模式先就无法响应。
其次 XR 模块下创建的 UI 画布 带了一个TrackedDeviceGraphicRaycaster(跟踪设备图形射线)组件
也就是这个组件接管了 普通 UI 画布的射线管理系统,要是没有这个组件 那么所有的 UI 事件都无法响应。

EventSystem 事件系统:
和普通 事件系统的差异化就是 XRUIInputModule(UI 输入 模块)原本的是 StandaloneInputModule组件。
如果你发现你的UI 射线无法点击也无法响应,就查看一下是不是这里出了问题。

射线碰撞事件响应 模块

在 Hierarchy 窗口 创建一个 Cube 更改名字(改不改都行)
在 Cube上添加 XR SImple Interactable:响应组件(不添加也行,不添加就代码添加)。

响应并实现:
(SelectEnterEventArgs Data) => { OnPointerClick(Data); }
可直接省略为 (Data) => { OnPointerClick(Data); }
 void Start()
    {
        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().selectEntered.AddListener((SelectEnterEventArgs Data) => { OnPointerClick(Data); });
        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().selectExited.AddListener((SelectExitEventArgs Data) => { OnPointerClick(Data); });
        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().hoverEntered.AddListener((HoverEnterEventArgs Data) => { OnPointerClick(Data); });
        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().hoverExited.AddListener((HoverExitEventArgs Data) => { OnPointerClick( Data); });

        //GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().selectEntered.AddListener((Data) => { OnPointerClick(Data); });
        //GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().selectExited.AddListener((Data) => { OnPointerClick(Data); });
        //GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().hoverEntered.AddListener((Data) => { OnPointerClick(Data); });
        //GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().hoverExited.AddListener((Data) => { OnPointerClick(Data); });

    }

       /// <summary>
    /// 射线进入并按键
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(SelectEnterEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线进入并按键";
    }

    /// <summary>
    /// 射线退出松开按键
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(SelectExitEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线进入松开按键";
    }

    /// <summary>
    /// 射线悬停
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(HoverEnterEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线悬停";
    }

    /// <summary>
    /// 射线悬停退出
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(HoverExitEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线悬停退出";
    }

Pico XR 健值操作

using System.Collections;
using System.Collections.Generic;
using Unity.XR.PXR;
using UnityEngine;
using UnityEngine.UI;
using UnityEngine.XR;
using UnityEngine.XR.Interaction.Toolkit;
/// <summary>
/// XR 健值操作
/// </summary>
public class PicoKeysOperation_ZH : MonoBehaviour
{
    [Header("左手控制器")]
    public XRController _LeftController;
    [Header("右手控制器")]
    public XRController _RightController;

    //摇杆移动输出值
    private Vector2 _Result;
    //移动物体
    private Transform _TargetTra;
    void Start()
    {
        _TargetTra = GameObject.Find("摇杆移动").transform;

        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().selectEntered.AddListener((Data) => { OnPointerClick(Data); });
        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().selectExited.AddListener((Data) => { OnPointerClick(Data); });
        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().hoverEntered.AddListener((Data) => { OnPointerClick(Data); });
        GameObject.Find("射线碰撞").GetComponent<XRSimpleInteractable>().hoverExited.AddListener((Data) => { OnPointerClick(Data); });
    }

    /// <summary>
    /// 射线进入并按键
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(SelectEnterEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线进入并按键";
    }

    /// <summary>
    /// 射线退出松开按键
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(SelectExitEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线进入松开按键";
    }

    /// <summary>
    /// 射线悬停
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(HoverEnterEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线悬停";
    }

    /// <summary>
    /// 射线悬停退出
    /// </summary>
    /// <param name="Data"></param>
    public void OnPointerClick(HoverExitEventArgs Data)
    {
        GameObject.Find("Text").GetComponent<Text>().text = "射线悬停退出";
    }

    void Update()
    {
        //是否成功返回
        //获取手部控制器的 摇杆值
        var _Success = _LeftController.inputDevice.TryGetFeatureValue(CommonUsages.primary2DAxis, out _Result);
        if (_Success)
        {
            //物体移动
            _TargetTra.position = new Vector3(_TargetTra.position.x + _Result.x * Time.deltaTime, _TargetTra.position.y, _TargetTra.position.z + _Result.y * Time.deltaTime);
        }


        // X键 按下
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool _PrimaryBool))
        {
            if (_PrimaryBool)
            {
                GameObject.Find("Text").GetComponent<Text>().text = "左手 按下 primaryButton X键";
            }

        }
        // A键 按下
        if (_RightController.inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool _RightPrimaryBool))
        {
            if (_RightPrimaryBool)
            {
                GameObject.Find("Text").GetComponent<Text>().text = "右手按下 primaryButton A键";
            }
        }
        //Y键 按下
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.secondaryButton, out bool _SecondaryBool))
        {
            if (_SecondaryBool)
            {
                GameObject.Find("Text").GetComponent<Text>().text = "按下 secondaryButton Y键";
            }

        }
        //B键 按下
        if (_RightController.inputDevice.TryGetFeatureValue(CommonUsages.secondaryButton, out bool _RightSecondaryBool))
        {
            if (_RightSecondaryBool)
            {
                GameObject.Find("Text").GetComponent<Text>().text = "按下 secondaryButton B键";
            }

        }


        //握柄键
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.grip, out float _Value))
        {
            if (_Value > 0.8f)
            {
                //Debug.LogWarning($"握柄键按下:{_Value}");
                GameObject.Find("Text").GetComponent<Text>().text = $"Grip键 按下:{_Value}";
            }
        }
        //握柄键 按下
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.gripButton, out bool _GripBool))
        {

            if (_GripBool)
            {
                //Debug.LogWarning("按下握柄键");
                GameObject.Find("Text").GetComponent<Text>().text = "按下握柄键";
            }
        }


        //扳机键
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.trigger, out float _TriggerValue))
        {
            if (_TriggerValue > 0.8f)
            {
                //Debug.LogWarning($"扳机键按下:{_TriggerValue}");
                GameObject.Find("Text").GetComponent<Text>().text = $"Trigger键 按下:{_TriggerValue}";
            }
        }
        //扳机键按下
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.triggerButton, out bool _TriggerBool))
        {
            if (_TriggerBool)
            {
                //Debug.LogWarning("按下扳机键");
                GameObject.Find("Text").GetComponent<Text>().text = "按下 Trigger键";
            }
        }

        //菜单键按下
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.menuButton, out bool _MenuBool))
        {
            if (_MenuBool)
            {
                Debug.LogWarning("按下菜单键");
                GameObject.Find("Text").GetComponent<Text>().text = "按下 Menu键";
            }

        }
        if (InputHelpers.IsPressed(_RightController.inputDevice, InputHelpers.Button.MenuButton, out bool _RightMenuBool))
        {
            if (_RightMenuBool)
            {
                Debug.LogWarning("右手按下 Menu键");
                GameObject.Find("Text").GetComponent<Text>().text = "右手按下 Menu键";
            }
        }

        //第二种方法
        //如果 扳机键按下的值超过 0.8f 就触发
        if (InputHelpers.IsPressed(_RightController.inputDevice, InputHelpers.Button.Trigger, out bool Ispressed, 0.8f))
        {
            if (Ispressed)
            {
                Debug.LogWarning(" _RightController Trigger键 超过 0.8f");

                GameObject.Find("Text").GetComponent<Text>().text = "Trigger键 超过 0.8f";
            }
        }

    }
}

Unity Pico 简单应用

    [Header("头盔")]
    public Transform _PicoCamera;

    [Header("Pico 左手控制")]
    public XRController _LeftController;
    [Header("Pico 右手控制")]
    public XRController _RightController;
    [
    Header("场景 UI")]
    public Canvas _SceneUI;

    [Header("背景音乐 ")]
    public AudioSource _Audio;

    //背景音乐控制
    private int _AudioNumber;

    // UI 偏移控制
    private Vector3 _PosVec=new Vector3(3.1f, 1.0f, 3.51f), _EulerAngles;
    //按键控制
    //为什么有这个按键控制,是因为XR 的按键响应不是只响应一次  而是会响应多次。
    //所以使用 双重布尔值进行 限制判定。
    bool _KeyDownBool, _KeyDownBool2, _KeyDownBoolMenue, _KeyDownBoolMenue2;
    
    private void Awake()
    {
        _Instants = this;

        Initialize();
    }
    
    void Update()
    {
        //X \ A 键  音乐控制
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool _LeftPrimaryBool) )
        {

            if (_LeftPrimaryBool)
            {
                _KeyDownBool = true;
            }

            if (!_LeftPrimaryBool && _KeyDownBool2)
            {
                //背景音乐 控制
                _AudioNumber++;
                if (_AudioNumber == 1)
                {
                    _Audio.Pause();
                    //文字转语音
                    StartCoroutine(TextToSpeech_ZH._World.GetAudioClip("背景音乐暂停。"));

                }
                else if (_AudioNumber == 2)
                {
                    _Audio.Play();
                    //文字转语音
                    StartCoroutine(TextToSpeech_ZH._World.GetAudioClip("背景音乐播放。"));
                    _AudioNumber = 0;
                }
            }
        }
        if (_RightController.inputDevice.TryGetFeatureValue(CommonUsages.primaryButton, out bool _RightPrimaryBool))
        {
            if (_RightPrimaryBool)
            {
                _KeyDownBool = true;
            }

            if (!_RightPrimaryBool&& _KeyDownBool)
            {
                //背景音乐 控制
                _AudioNumber++;
                if (_AudioNumber == 1)
                {
                    _Audio.Pause();
                    //文字转语音
                    StartCoroutine(TextToSpeech_ZH._World.GetAudioClip("背景音乐暂停。"));
                }
                else if (_AudioNumber == 2)
                {
                    _Audio.Play();
                    //文字转语音
                    StartCoroutine(TextToSpeech_ZH._World.GetAudioClip("背景音乐播放。"));
                    _AudioNumber = 0;
                }
                _KeyDownBool = false;
            }
        }

        //菜单键 按下
        if (_LeftController.inputDevice.TryGetFeatureValue(CommonUsages.menuButton, out bool _LeftMenuBool))
        {
            if (_LeftMenuBool)
            {
                _KeyDownBoolMenue2 = true;
            }
            if (!_LeftMenuBool&& _KeyDownBoolMenue2)
            {
                //菜单 UI 显示
                _SceneUI.gameObject.SetActive(true);

                //菜单 UI 固定位置显示
                _SceneUI.transform.position = new Vector3(_PicoCamera.parent.parent.position.x, _PicoCamera.parent.parent.position.y + _PosVec.y, _PicoCamera.parent.parent.position.z) + _PicoCamera.parent.parent.forward * _PosVec.z;
                _SceneUI.transform.LookAt(new Vector3(_PicoCamera.parent.parent.position.x, _SceneUI.transform.position.y, _PicoCamera.parent.parent.position.z) + _EulerAngles);

				//文字转语音
                StartCoroutine(TextToSpeech_ZH._World.GetAudioClip("菜单已打开"));

                _KeyDownBoolMenue = false;
            }
        }
        if (_RightController.inputDevice.TryGetFeatureValue(CommonUsages.menuButton, out bool _RightMenuBool))
        {
            if (_RightMenuBool)
            {
                _KeyDownBoolMenue = true;
            }

            if (!_RightMenuBool&& _KeyDownBoolMenue)
            {
                //菜单 UI 显示
                _SceneUI.gameObject.SetActive(true);

                //菜单 UI 固定位置显示
                _SceneUI.transform.position = new Vector3(_PicoCamera.parent.parent.position.x, _PicoCamera.parent.parent.position.y + _PosVec.y, _PicoCamera.parent.parent.position.z) + _PicoCamera.parent.parent.forward * _PosVec.z;
                _SceneUI.transform.LookAt(new Vector3(_PicoCamera.parent.parent.position.x, _SceneUI.transform.position.y, _PicoCamera.parent.parent.position.z) + _EulerAngles);

				//文字转语音
                StartCoroutine(TextToSpeech_ZH._World.GetAudioClip("菜单已打开"));

                _KeyDownBoolMenue = false;
            }
        }
    }
 /// <summary>
    /// 初始化
    /// </summary>
    public void Initialize()
    {
    	// PicoCamera 
        if (_PicoCamera == null)
        {
            _PicoCamera = Camera.main.transform;
        }
        //左手控制器
        if (_LeftController == null)
        {
            _LeftController = _PicoCamera.parent.Find("LeftHand Controller").GetComponent<XRController>();
        }
        //右手控制器
        if (_RightController==null)
        {
            _RightController = _PicoCamera.parent.Find("RightHand Controller").GetComponent<XRController>();
        }
		//背景音乐
        if (_Audio == null)
        {
            _Audio = GameObject.Find("---------- Audio ----------").transform.Find("Audio").GetComponent<AudioSource>();
        }

        //菜单 UI
        if (_SceneUI == null)
        {
            _SceneUI = GameObject.Find("菜单 UI").GetComponent<Canvas>();
            _SceneUI.gameObject.SetActive(false);
        }
        else
        {
            _SceneUI.gameObject.SetActive(false);
        }      
    }

暂时先这样吧,如果有时间的话就会更新,实在看不明白就留言,看到我会回复的。
路漫漫其修远兮,与君共勉。

有关Unity Pico Neo3 基础开发流程的更多相关文章

  1. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  2. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  3. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  4. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  5. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  6. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  7. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  8. postman接口测试工具-基础使用教程 - 2

    1.postman介绍Postman一款非常流行的API调试工具。其实,开发人员用的更多。因为测试人员做接口测试会有更多选择,例如Jmeter、soapUI等。不过,对于开发过程中去调试接口,Postman确实足够的简单方便,而且功能强大。2.下载安装官网地址:https://www.postman.com/下载完成后双击安装吧,安装过程极其简单,无需任何操作3.使用教程这里以百度为例,工具使用简单,填写URL地址即可发送请求,在下方查看响应结果和响应状态码常用方法都有支持请求方法:getpostputdeleteGet、Post、Put与Delete的作用get:请求方法一般是用于数据查询,

  9. 软件测试基础 - 2

    Ⅰ软件测试基础一、软件测试基础理论1、软件测试的必要性所有的产品或者服务上线都需要测试2、测试的发展过程3、什么是软件测试找bug,发现缺陷4、测试的定义使用人工或自动的手段来运行或者测试某个系统的过程。目的在于检测它是否满足规定的需求。弄清预期结果和实际结果的差别。5、测试的目的以最小的人力、物力和时间找出软件中潜在的错误和缺陷6、测试的原则28原则:20%的主要功能要重点测(eg:支付宝的支付功能,其他功能都是次要的)80%的错误存在于20%的代码中7、测试标准8、测试的基本要求功能测试性能测试安全性测试兼容性测试易用性测试外观界面测试可靠性测试二、质量模型衡量一个优秀软件的维度①功能性功

  10. 微信小程序开发入门与实战(Behaviors使用) - 2

    @作者:SYFStrive @博客首页:HomePage📜:微信小程序📌:个人社区(欢迎大佬们加入)👉:社区链接🔗📌:觉得文章不错可以点点关注👉:专栏连接🔗💃:感谢支持,学累了可以先看小段由小胖给大家带来的街舞👉微信小程序(🔥)目录自定义组件-behaviors    1、什么是behaviors    2、behaviors的工作方式    3、创建behavior    4、导入并使用behavior    5、behavior中所有可用的节点    6、同名字段的覆盖和组合规则总结最后自定义组件-behaviors    1、什么是behaviorsbehaviors是小程序中,用于实现

随机推荐