Unity引擎开发:VR渲染技术_(17).VR中的空间感知与交互

VR中的空间感知与交互

在虚拟现实(VR)游戏中,空间感知与交互是核心功能之一,它们直接影响玩家的沉浸感和游戏体验。本节将详细介绍如何在Unity引擎中实现这些功能,包括空间定位、手部追踪、物体抓取和释放、以及碰撞检测等。我们将通过具体的代码示例来帮助你更好地理解和实现这些功能。

空间定位(Spatial Localization)

空间定位是指VR系统能够准确地跟踪用户在虚拟环境中的位置和方向。在Unity中,这通常通过外部追踪设备(如SteamVR或Oculus追踪系统)实现。我们将介绍如何在Unity中设置和使用这些追踪设备。

设置追踪设备

  1. 安装必要的插件

    • 首先,确保你的Unity项目中安装了与你使用的VR设备对应的插件。例如,如果你使用的是HTC Vive或Valve Index,你需要安装SteamVR插件。

    • 打开Unity Hub,创建一个新的项目或打开现有的项目。

    • 在Unity编辑器中,进入Window -> Package Manager,搜索并安装XR Plugin ManagementXR Interaction Toolkit

  2. 配置XR设置

    • 在Unity编辑器中,进入Edit -> Project Settings -> XR Plugin Management

    • 选择Standalone(Windows、Mac或Linux)或Android(如果目标平台是移动设备)。

    • 启用相应的VR SDK,例如SteamVR或Oculus。

  3. 创建追踪点

    • Hierarchy窗口中,创建一个新的空对象,命名为TrackingOrigin

    • XR Rig组件添加到TrackingOrigin对象上。这个组件会自动设置追踪点,并帮助你管理头显和手柄的位置。


// 创建追踪点的脚本示例

using UnityEngine;

using UnityEngine.XR;



public class TrackingSetup : MonoBehaviour

{

    // 在Awake方法中初始化追踪点

    void Awake()

    {

        // 创建追踪点

        GameObject trackingOrigin = new GameObject("TrackingOrigin");

        trackingOrigin.transform.position = Vector3.zero;

        trackingOrigin.transform.rotation = Quaternion.identity;



        // 添加XR Rig组件

        XR Rig rig = trackingOrigin.AddComponent<XR Rig>();

        rig.trackingOriginMode = TrackingOriginModeFlags.Floor;

    }

}

获取头显和手柄的位置和方向

  1. 头显的位置和方向

    • 使用InputTracking.GetLocalPositionInputTracking.GetLocalRotation方法获取头显的位置和方向。

// 获取头显位置和方向的脚本示例

using UnityEngine;

using UnityEngine.XR;



public class HeadsetPosition : MonoBehaviour

{

    void Update()

    {

        // 获取头显的位置

        Vector3 headsetPosition = InputTracking.GetLocalPosition(XRNode.Head);



        // 获取头显的方向

        Quaternion headsetRotation = InputTracking.GetLocalRotation(XRNode.Head);



        // 输出头显的位置和方向

        Debug.Log("Headset Position: " + headsetPosition);

        Debug.Log("Headset Rotation: " + headsetRotation);

    }

}

  1. 手柄的位置和方向

    • 使用InputTracking.GetLocalPositionInputTracking.GetLocalRotation方法获取手柄的位置和方向。

// 获取手柄位置和方向的脚本示例

using UnityEngine;

using UnityEngine.XR;



public class ControllerPosition : MonoBehaviour

{

    void Update()

    {

        // 获取左控制器的位置

        Vector3 leftControllerPosition = InputTracking.GetLocalPosition(XRNode.LeftHand);



        // 获取左控制器的方向

        Quaternion leftControllerRotation = InputTracking.GetLocalRotation(XRNode.LeftHand);



        // 获取右控制器的位置

        Vector3 rightControllerPosition = InputTracking.GetLocalPosition(XRNode.RightHand);



        // 获取右控制器的方向

        Quaternion rightControllerRotation = InputTracking.GetLocalRotation(XRNode.RightHand);



        // 输出手柄的位置和方向

        Debug.Log("Left Controller Position: " + leftControllerPosition);

        Debug.Log("Left Controller Rotation: " + leftControllerRotation);

        Debug.Log("Right Controller Position: " + rightControllerPosition);

        Debug.Log("Right Controller Rotation: " + rightControllerRotation);

    }

}

手部追踪(Hand Tracking)

手部追踪是指VR系统能够准确地跟踪用户的手部动作,并在虚拟环境中进行相应的显示和交互。Unity支持多种手部追踪方案,包括使用手柄和无手柄的裸手追踪。

使用手柄的手部追踪

  1. 设置输入映射

    • 在Unity编辑器中,进入Edit -> Project Settings -> Input Manager

    • 添加新的输入轴,用于手柄的触发器和按钮输入。

  2. 创建手柄控制器模型

    • Hierarchy窗口中,创建一个新的空对象,命名为LeftControllerRightController

    • 将手柄模型(通常是FBX文件)拖放到这些控制器对象上。

    • 添加XR Controller Model组件,并配置相应的手柄模型。

  3. 获取手柄输入

    • 使用Input.GetAxisInput.GetButton方法获取手柄的触发器和按钮输入。

// 获取手柄输入的脚本示例

using UnityEngine;



public class ControllerInput : MonoBehaviour

{

    // 左手柄的触发器输入

    public float leftTriggerValue;



    // 右手柄的触发器输入

    public float rightTriggerValue;



    void Update()

    {

        // 获取左控制器的触发器输入

        leftTriggerValue = Input.GetAxis("LeftTrigger");



        // 获取右控制器的触发器输入

        rightTriggerValue = Input.GetAxis("RightTrigger");



        // 检查左控制器的按钮输入

        if (Input.GetButton("LeftGrip"))

        {

            Debug.Log("Left Grip Button Pressed");

        }



        // 检查右控制器的按钮输入

        if (Input.GetButton("RightGrip"))

        {

            Debug.Log("Right Grip Button Pressed");

        }

    }

}

无手柄的裸手追踪

  1. 安装手部追踪插件

    • 例如,如果你使用的是Oculus设备,需要安装Oculus Integration插件,并启用手部追踪功能。

    • 在Unity编辑器中,进入Window -> Package Manager,搜索并安装Oculus Integration

  2. 配置手部追踪

    • Oculus菜单中,选择Configure -> Project Settings,启用手部追踪功能。
  3. 获取手部追踪数据

    • 使用OVRInput.GetLocalControllerPose方法获取手部的位置和方向。

// 获取无手柄手部追踪数据的脚本示例

using UnityEngine;

using Oculus;



public class HandTracking : MonoBehaviour

{

    void Update()

    {

        // 获取左控制器的位置和方向

        OVRPose leftHandPose = OVRInput.GetLocalControllerPose(OVRInput.Controller.LTouch);

        Vector3 leftHandPosition = leftHandPose.position;

        Quaternion leftHandRotation = leftHandPose.orientation;



        // 获取右控制器的位置和方向

        OVRPose rightHandPose = OVRInput.GetLocalControllerPose(OVRInput.Controller.RTouch);

        Vector3 rightHandPosition = rightHandPose.position;

        Quaternion rightHandRotation = rightHandPose.orientation;



        // 输出手部的位置和方向

        Debug.Log("Left Hand Position: " + leftHandPosition);

        Debug.Log("Left Hand Rotation: " + leftHandRotation);

        Debug.Log("Right Hand Position: " + rightHandPosition);

        Debug.Log("Right Hand Rotation: " + rightHandRotation);

    }

}

物体抓取和释放(Object Grabbing and Releasing)

在VR游戏中,物体抓取和释放是常见的交互方式。我们将介绍如何在Unity中实现这些功能,包括使用手柄和手部追踪。

使用手柄抓取和释放物体

  1. 创建抓取点

    • Hierarchy窗口中,创建一个新的空对象,命名为GrabPoint,并将其作为手柄控制器的子对象。

    • 添加Collider组件,用于检测物体碰撞。

  2. 编写抓取和释放脚本

    • 使用OnCollisionEnterOnCollisionExit方法检测物体碰撞。

    • 使用AddForceAddTorque方法模拟抓取和释放物体的物理效果。


// 抓取和释放物体的脚本示例

using UnityEngine;



public class ObjectGrabbing : MonoBehaviour

{

    // 抓取的物体

    public GameObject grabbedObject;



    // 抓取点的刚体组件

    private Rigidbody grabPointRigidbody;



    // 抓取点的 Collider 组件

    private Collider grabPointCollider;



    void Start()

    {

        // 获取抓取点的刚体和 Collider 组件

        grabPointRigidbody = GetComponent<Rigidbody>();

        grabPointCollider = GetComponent<Collider>();

    }



    void Update()

    {

        // 检查触发器输入

        if (Input.GetAxis("LeftTrigger") > 0.5f && grabbedObject == null)

        {

            // 尝试抓取物体

            GrabObject();

        }



        if (Input.GetAxis("LeftTrigger") < 0.1f && grabbedObject != null)

        {

            // 释放物体

            ReleaseObject();

        }

    }



    void GrabObject()

    {

        // 检测碰撞的物体

        Collider[] colliders = Physics.OverlapSphere(transform.position, 0.1f);



        foreach (Collider collider in colliders)

        {

            // 检查物体是否有 Rigidbody 组件

            Rigidbody rb = collider.GetComponent<Rigidbody>();

            if (rb != null)

            {

                // 抓取物体

                grabbedObject = collider.gameObject;

                rb.isKinematic = true;

                rb.transform.SetParent(transform);

                break;

            }

        }

    }



    void ReleaseObject()

    {

        if (grabbedObject != null)

        {

            // 释放物体

            Rigidbody rb = grabbedObject.GetComponent<Rigidbody>();

            rb.isKinematic = false;

            rb.transform.SetParent(null);

            grabbedObject = null;

        }

    }

}

使用手部追踪抓取和释放物体

  1. 检测手部与物体的碰撞

    • 使用LineCastSphereCast方法检测手部与物体的碰撞。
  2. 编写抓取和释放脚本

    • 使用OnHandOverlaps方法检测手部与物体的重叠。

    • 使用OnHandPose方法检测手部姿势,以确定是否进行抓取或释放操作。


// 抓取和释放物体的脚本示例(手部追踪)

using UnityEngine;

using Oculus;



public class HandObjectGrabbing : MonoBehaviour

{

    // 抓取的物体

    public GameObject grabbedObject;



    // 抓取点的刚体组件

    private Rigidbody grabPointRigidbody;



    // 抓取点的 Collider 组件

    private Collider grabPointCollider;



    void Start()

    {

        // 获取抓取点的刚体和 Collider 组件

        grabPointRigidbody = GetComponent<Rigidbody>();

        grabPointCollider = GetComponent<Collider>();

    }



    void Update()

    {

        // 检查手部姿势

        OVRInput.HandPose leftHandPose = OVRInput.GetActiveControllerPose(OVRInput.Controller.LTouch).handPose;

        OVRInput.HandPose rightHandPose = OVRInput.GetActiveControllerPose(OVRInput.Controller.RTouch).handPose;



        if (leftHandPose == OVRInput.HandPose.Grab && grabbedObject == null)

        {

            // 尝试抓取物体

            GrabObject(OVRInput.GetLocalControllerPose(OVRInput.Controller.LTouch).position, OVRInput.GetLocalControllerPose(OVRInput.Controller.LTouch).orientation);

        }



        if (leftHandPose == OVRInput.HandPose.Open && grabbedObject != null)

        {

            // 释放物体

            ReleaseObject();

        }



        if (rightHandPose == OVRInput.HandPose.Grab && grabbedObject == null)

        {

            // 尝试抓取物体

            GrabObject(OVRInput.GetLocalControllerPose(OVRInput.Controller.RTouch).position, OVRInput.GetLocalControllerPose(OVRInput.Controller.RTouch).orientation);

        }



        if (rightHandPose == OVRInput.HandPose.Open && grabbedObject != null)

        {

            // 释放物体

            ReleaseObject();

        }

    }



    void GrabObject(Vector3 handPosition, Quaternion handRotation)

    {

        // 检测手部与物体的碰撞

        RaycastHit hit;

        if (Physics.SphereCast(handPosition, 0.05f, handRotation * Vector3.forward, out hit, 0.1f))

        {

            // 检查物体是否有 Rigidbody 组件

            Rigidbody rb = hit.collider.GetComponent<Rigidbody>();

            if (rb != null)

            {

                // 抓取物体

                grabbedObject = hit.collider.gameObject;

                rb.isKinematic = true;

                rb.transform.SetParent(transform);

            }

        }

    }



    void ReleaseObject()

    {

        if (grabbedObject != null)

        {

            // 释放物体

            Rigidbody rb = grabbedObject.GetComponent<Rigidbody>();

            rb.isKinematic = false;

            rb.transform.SetParent(null);

            grabbedObject = null;

        }

    }

}

碰撞检测(Collision Detection)

在VR游戏中,碰撞检测是确保物体之间正确互动的关键。我们将介绍如何在Unity中实现基本的碰撞检测。

基本碰撞检测

  1. 创建物体

    • Hierarchy窗口中,创建两个物体,例如一个立方体和一个球体。

    • 为每个物体添加Collider组件。

  2. 编写碰撞检测脚本

    • 使用OnCollisionEnterOnCollisionStayOnCollisionExit方法检测物体之间的碰撞。

// 基本碰撞检测的脚本示例

using UnityEngine;



public class CollisionDetection : MonoBehaviour

{

    void OnCollisionEnter(Collision collision)

    {

        // 碰撞开始时的处理

        Debug.Log("Collision started with: " + collision.gameObject.name);

    }



    void OnCollisionStay(Collision collision)

    {

        // 碰撞持续时的处理

        Debug.Log("Collision staying with: " + collision.gameObject.name);

    }



    void OnCollisionExit(Collision collision)

    {

        // 碰撞结束时的处理

        Debug.Log("Collision ended with: " + collision.gameObject.name);

    }

}

触发器碰撞检测

  1. 设置触发器

    • Collider组件的Is Trigger属性设置为true

    • 为触发器对象添加Rigidbody组件,并将其Is Kinematic属性设置为true

  2. 编写触发器碰撞检测脚本

    • 使用OnTriggerEnterOnTriggerStayOnTriggerExit方法检测触发器碰撞。

// 触发器碰撞检测的脚本示例

using UnityEngine;



public class TriggerCollisionDetection : MonoBehaviour

{

    void OnTriggerEnter(Collider other)

    {

        // 触发器碰撞开始时的处理

        Debug.Log("Trigger collision started with: " + other.gameObject.name);

    }



    void OnTriggerStay(Collider other)

    {

        // 触发器碰撞持续时的处理

        Debug.Log("Trigger collision staying with: " + other.gameObject.name);

    }



    void OnTriggerExit(Collider other)

    {

        // 触发器碰撞结束时的处理

        Debug.Log("Trigger collision ended with: " + other.gameObject.name);

    }

}

复杂碰撞检测

  1. 使用复合碰撞器

    • 为复杂的物体添加多个Collider组件,形成复合碰撞器。

    • 例如,一个机器人模型可以由多个网格碰撞器组成。

  2. 编写复杂碰撞检测脚本

    • 使用Physics.Raycast方法检测射线与物体的碰撞。

    • 使用Physics.SphereCast方法检测球形射线与物体的碰撞。


// 复杂碰撞检测的脚本示例

using UnityEngine;



public class ComplexCollisionDetection : MonoBehaviour

{

    // 射线的长度

    public float rayLength = 10f;



    // 射线的源点

    public Transform raySource;



    void Update()

    {

        // 射线检测

        Ray ray = new Ray(raySource.position, raySource.forward);

        RaycastHit hit;



        if (Physics.Raycast(ray, out hit, rayLength))

        {

            // 检测到射线碰撞

            Debug.Log("Ray hit: " + hit.collider.gameObject.name);

        }



        // 球形射线检测

        Vector3 spherePosition = raySource.position + raySource.forward * 0.5f;

        if (Physics.SphereCast(spherePosition, 0.1f, raySource.forward, out hit, 5f))

        {

            // 检测到球形射线碰撞

            Debug.Log("Sphere ray hit: " + hit.collider.gameObject.name);

        }

    }

}

交互示例(Interaction Examples)

为了更好地理解空间感知和交互的实现,我们将通过几个具体的交互示例来展示如何在Unity中实现这些功能。

示例1:开门和关门

  1. 创建门模型

    • Hierarchy窗口中,创建一个门模型,例如一个旋转门。

    • 为门模型添加Hinge Joint组件,以实现门的旋转。

  2. 编写门的交互脚本

    • 使用手柄的触发器和按钮输入来控制门的开合。

// 门的交互脚本示例

using UnityEngine;



public class DoorInteraction : MonoBehaviour

{

    // 门的 Hinge Joint 组件

    public HingeJoint hingeJoint;



    // 门的打开角度

    public float openAngle = 90f;



    // 门的关闭角度

    public float closeAngle = 0f;



    // 门的旋转速度

    public float rotationSpeed = 5f;



    // 门的目标角度

    private float targetAngle;



    void Start()

    {

        // 初始化门的目标角度为关闭状态

        targetAngle = closeAngle;

    }



    void Update()

    {

        // 检查触发器输入

        if (Input.GetAxis("LeftTrigger") > 0.5f)

        {

            // 打开门

            targetAngle = openAngle;

        }



        if (Input.GetAxis("LeftTrigger") < 0.1f)

        {

            // 关闭门

            targetAngle = closeAngle;

        }



        // 逐步旋转门到目标角度

        RotateDoor();

    }



    void RotateDoor()

    {

        // 获取门的当前角度

        float currentAngle = hingeJoint.angle;



        // 计算角度差

        float angleDifference = targetAngle - currentAngle;



        // 如果角度差大于0.1度,继续旋转

        if (Mathf.Abs(angleDifference) > 0.1f)

        {

            // 计算旋转方向

            float rotationDirection = angleDifference > 0 ? 1 : -1;



            // 逐步旋转门

            hingeJoint.useMotor = true;

            JointMotor motor = hingeJoint.motor;

            motor.targetVelocity = rotationSpeed * rotationDirection;

            hingeJoint.motor = motor;

        }

        else

        {

            // 停止旋转

            hingeJoint.useMotor = false;

            JointMotor motor = hingeJoint.motor;

            motor.targetVelocity = 0;

            hingeJoint.motor = motor;

        }

    }

}

示例2:物体拾取和放置

  1. 创建可拾取的物体

    • Hierarchy窗口中,创建一个可拾取的物体,例如一个立方体。

    • 为物体添加RigidbodyCollider组件。

  2. 编写物体拾取和放置脚本

    • 使用手柄的触发器和按钮输入来控制物体的拾取和放置。

// 物体拾取和放置的脚本示例

using UnityEngine;



public class ObjectPickupAndPlace : MonoBehaviour

{

    // 抓取的物体

    public GameObject grabbedObject;



    // 抓取点的刚体组件

    private Rigidbody grabPointRigidbody;



    // 抓取点的 Collider 组件

    private Collider grabPointCollider;



    void Start()

    {

        // 获取抓取点的刚体和 Collider 组件

        grabPointRigidbody = GetComponent<Rigidbody>();

        grabPointCollider = GetComponent<Collider>();

    }



    void Update()

    {

        // 检查触发器输入

        if (Input.GetAxis("LeftTrigger") > 0.5f && grabbedObject == null)

        {

            // 尝试抓取物体

            GrabObject();

        }



        if (Input.GetAxis("LeftTrigger") < 0.1f && grabbedObject != null)

        {

            // 释放物体

            ReleaseObject();

        }

    }



    void GrabObject()

    {

        // 检测碰撞的物体

        Collider[] colliders = Physics.OverlapSphere(transform.position, 0.1f);



        foreach (Collider collider in colliders)

        {

            // 检查物体是否有 Rigidbody 组件

            Rigidbody rb = collider.GetComponent<Rigidbody>();

            if (rb != null)

            {

                // 抓取物体

                grabbedObject = collider.gameObject;

                rb.isKinematic = true;

                rb.transform.SetParent(transform);

                break;

            }

        }

    }



    void ReleaseObject()

    {

        if (grabbedObject != null)

        {

            // 释放物体

            Rigidbody rb = grabbedObject.GetComponent<Rigidbody>();

            rb.isKinematic = false;

            rb.transform.SetParent(null);

            grabbedObject = null;

        }

    }

}

示例3:虚拟按钮交互

  1. 创建虚拟按钮模型

    • Hierarchy窗口中,创建一个虚拟按钮模型,例如一个平面按钮。

    • 为按钮模型添加Collider组件,并将其设置为触发器。

  2. 编写虚拟按钮交互脚本

    • 使用手柄或手部追踪的输入来检测按钮的按下和释放。

// 虚拟按钮交互脚本示例

using UnityEngine;



public class VirtualButton : MonoBehaviour

{

    // 按钮按下时的处理

    void OnTriggerEnter(Collider other)

    {

        if (other.CompareTag("Hand"))

        {

            Debug.Log("Button pressed");

            // 执行按钮按下时的逻辑,例如播放音效、触发事件等

        }

    }



    // 按钮释放时的处理

    void OnTriggerExit(Collider other)

    {

        if (other.CompareTag("Hand"))

        {

            Debug.Log("Button released");

            // 执行按钮释放时的逻辑,例如停止音效、取消事件等

        }

    }

}

示例4:虚拟滑块交互

  1. 创建虚拟滑块模型

    • Hierarchy窗口中,创建一个虚拟滑块模型,例如一个滑动条。

    • 为滑块模型添加Collider组件,并将其设置为触发器。

    • 为滑动条的背景和滑块添加相应的模型。

  2. 编写虚拟滑块交互脚本

    • 使用手柄或手部追踪的输入来检测滑块的滑动。

// 虚拟滑块交互脚本示例

using UnityEngine;



public class VirtualSlider : MonoBehaviour

{

    // 滑块的移动范围

    public Vector3 minPosition;

    public Vector3 maxPosition;



    // 滑块当前的位置

    private Vector3 sliderPosition;



    // 滑块是否被抓住

    private bool isGrabbed = false;



    void Start()

    {

        // 初始化滑块的位置

        sliderPosition = transform.position;

    }



    void Update()

    {

        // 检查触发器输入

        if (Input.GetAxis("LeftTrigger") > 0.5f)

        {

            // 尝试抓取滑块

            GrabSlider();

        }



        if (Input.GetAxis("LeftTrigger") < 0.1f)

        {

            // 释放滑块

            ReleaseSlider();

        }



        // 如果滑块被抓住,更新滑块的位置

        if (isGrabbed)

        {

            MoveSlider();

        }

    }



    void GrabSlider()

    {

        // 检测碰撞的物体

        Collider[] colliders = Physics.OverlapSphere(transform.position, 0.1f);



        foreach (Collider collider in colliders)

        {

            if (collider.CompareTag("Hand"))

            {

                // 抓取滑块

                isGrabbed = true;

                break;

            }

        }

    }



    void ReleaseSlider()

    {

        if (isGrabbed)

        {

            // 释放滑块

            isGrabbed = false;

        }

    }



    void MoveSlider()

    {

        // 获取手柄的位置

        Vector3 handPosition = InputTracking.GetLocalPosition(XRNode.LeftHand);



        // 计算滑块的新位置

        sliderPosition = new Vector3(

            Mathf.Clamp(handPosition.x, minPosition.x, maxPosition.x),

            Mathf.Clamp(handPosition.y, minPosition.y, maxPosition.y),

            Mathf.Clamp(handPosition.z, minPosition.z, maxPosition.z)

        );



        // 更新滑块的位置

        transform.position = sliderPosition;

    }

}

总结

通过上述示例,我们展示了如何在Unity引擎中实现VR游戏中的空间感知与交互功能。这些功能包括空间定位、手部追踪、物体抓取和释放、以及碰撞检测。每种功能的实现都依赖于Unity中的XR插件和物理引擎,通过合理的配置和脚本编写,可以为玩家提供更加沉浸和真实的交互体验。

进一步优化

  1. 平滑移动

    • 使用插值(lerp)方法平滑移动物体,避免突兀的跳跃。
  2. 物理效果

    • 为抓取和释放物体添加物理效果,例如碰撞声音、振动反馈等,增强交互的真实感。
  3. UI交互

    • 为虚拟按钮和滑块添加动画和音效,提高用户的交互体验。
  4. 多平台支持

    • 确保代码在不同平台(如SteamVR、Oculus、Windows Mixed Reality)上都能正常工作,进行必要的适配和优化。

希望这些示例和代码能帮助你在Unity中开发出更加精彩的VR游戏。如果你有任何问题或需要进一步的帮助,请随时查阅Unity官方文档或社区资源。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值