Unity3D作业六:打靶游戏

1:游戏要求:

1.1游戏内容要求:

  • 靶对象为 5 环,按环计分;
  • 箭对象,射中后要插在靶上
  • 增强要求:射中后,箭对象产生颤抖效果,到下一次射击 或 1秒以后
  • 游戏仅一轮,无限 trials;
  • 增强要求:添加一个风向和强度标志,提高难度

1.2游戏编程要求

  • 使用Adapter模式:将一个类的接口转换成客户希望的另外一个接口,使得原本由于接口不兼容而不能一起工作的那些类能一起工作。

2:游戏对象预制:

2.1:靶子Target:

靶子是由五个同心的圆柱体组成的预制体,每个圆柱体代表一个环,每个环的分值不一样。并且每个圆柱体都需要添加MeshCollider组件,并设置为IsTrigger,用来检测箭是否中靶。详细设置:
在这里插入图片描述

2.2:箭Arrow:

箭主要有两部分,箭身以及子对象箭头,设置标签为arrow详细设置如下图:

  • 箭身的设置:
    在这里插入图片描述
  • 箭头的设置:
    在这里插入图片描述

2.3: 弓bow:

弓同样由两部分组成,弓本身以及一个架在弓上的箭,所以需要添加一个箭的预制:在这里插入图片描述

3:编程:

在这里插入图片描述

3.1 Adapter模式:

1:基类SSAction

箭的多种动作类的共同父类,使得可以统一管理箭的多种动作:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSAction : ScriptableObject
{
    public bool enable = true;                      //是否正在进行此动作
    public bool destroy = false;                    //是否需要被销毁
    public GameObject gameobject;                   //动作对象
    public Transform transform;                     //动作对象的transform
    public ISSActionCallback callback;              //动作完成后的消息通知者

    protected SSAction() { }
    //子类可以使用下面这两个函数
    public virtual void Start()
    {
        throw new System.NotImplementedException();
    }
    public virtual void Update()
    {
        throw new System.NotImplementedException();
    }
    public virtual void FixedUpdate()
    {
        throw new System.NotImplementedException();
    }
}

2:箭的射击动作 ArrowShootAction.cs

通过一个作为初始动力的冲量pulseForce和一个持续的风力windForce驱使箭飞行。在箭中靶或者脱靶,即飞行动作结束之后,回调箭的颤抖动作:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ArrowShootAction : SSAction
{
    private Vector3 pulseForce;  //射箭提供的冲量
    private Vector3 windForce;   //风力
    private ArrowShootAction(){}

    public static ArrowShootAction GetSSAction(Vector3 wind)
    {
        ArrowShootAction shootarrow = CreateInstance<ArrowShootAction>();
        shootarrow.pulseForce = new Vector3(0,0,25);
        shootarrow.windForce = wind;
        return shootarrow;
    }
    // Start is called before the first frame update
    public override void Start()
    {
        //摆脱跟随弓移动
        gameobject.transform.parent = null;
        gameobject.GetComponent<Rigidbody>().velocity = Vector3.zero;
        //添加初始的冲量
        gameobject.GetComponent<Rigidbody>().AddForce(pulseForce,ForceMode.Impulse);
        //关闭运动学控制
        gameobject.GetComponent<Rigidbody>().isKinematic = false;
    }

    // Update is called once per frame
    public  override void Update()
    {
        
    }

    public override void FixedUpdate(){
        //添加风力
        this.gameobject.GetComponent<Rigidbody>().AddForce(windForce,ForceMode.Force);
        //射击动作结束,即中靶或者脱靶,回调颤抖动作
        if(this.transform.position.z > 35 || this.gameobject.tag == "onTarget"  )
        {
            this.destroy = true;
            this.callback.SSActionEvent(this,1,this.gameobject);
        }
    }
}

3:ArrowTrembleAction 箭的颤抖动作:

围绕箭的头部进行细微的运动,实现颤抖的效果,该动作在结束时也会调用回调函数,不过没有下一步的动作,所以回调的函数为空;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ArrowTrembleAction : SSAction
{
    float tremble_radius = 1.2f;//颤抖的程度
    float tremble_time = 1.0f; //颤抖的时间

    Vector3 arrow_pos;   //箭的原位置

    private ArrowTrembleAction(){}
    //放回一个颤抖的动作
    public static ArrowTrembleAction GetSSAction()
    {
        ArrowTrembleAction tremble_action = CreateInstance<ArrowTrembleAction>();
        return tremble_action;

    }

    // Start is called before the first frame update
    public override void Start()
    {
        //得到箭中靶时的位置
        arrow_pos = this.transform.position;
    }
    //实现箭的颤抖动作
    // Update is called once per frame
    public override  void Update()
    {   //更新时间,得到剩余的颤抖时间
        tremble_time -= Time.deltaTime;
        //需要继续颤抖
        if(tremble_time > 0){
            ///获取头部的位置
           Vector3 head_pos = this.transform.GetChild(0).position;
           //围绕箭头颤抖
           this.transform.RotateAround(head_pos,tremble_radius);  
        }
        else{
            //将箭返回一开始的位置
            transform.position = arrow_pos;
            //开始销毁动作
            this.destroy = true;
            //通过接口回调,调用新的动作,实际上没有新的动作,回调为空
            this.callback.SSActionEvent(this);
        }
    }

    public override void FixedUpdate()
    {
        
    }
}

4:SSActionManage统一的动作管理接口:

该类维护着一个动作队列actions,在每次更新时,即Update()或者FixedUpdate(),都会添加新的动作,或者删除需要销毁的动作,并运行需要更新的动作;同时提供了一个添加新动作的方法RunAction和回调接口SSActionEvent;

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SSActionManager : MonoBehaviour,ISSActionCallback
{
    private Dictionary<int,SSAction> actions = new Dictionary<int,SSAction>();  //执行动作的字典集
    //等待添加的动作
    private List<SSAction> waitingAdd = new List<SSAction>();
    //等待销毁的动作
    private List<int> waitingDelete = new List<int>();
    // Start is called before the first frame update
     

    // Update is called once per frame
    protected void Update()
    {
        //添加新增的动作
        for(int i = 0; i < waitingAdd.Count; ++i)
        {
            actions[waitingAdd[i].GetInstanceID()] = waitingAdd[i];
        }
        waitingAdd.Clear();
        //销毁需要删除的动作
        foreach (KeyValuePair<int,SSAction> kv in actions){
            SSAction t_ac = kv.Value;
            if(t_ac.destroy)
            {
                waitingDelete.Add(t_ac.GetInstanceID());

            }
            //动作不需要销毁则更新
            else if(t_ac.enable)
            {
                t_ac.Update();
            }
        }
        //销毁动作
        foreach (int key in waitingDelete){
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        waitingDelete.Clear();
    }

    protected void FixedUpdate()
    {
                for(int i = 0; i < waitingAdd.Count; ++i)
        {
            actions[waitingAdd[i].GetInstanceID()] = waitingAdd[i];
        }
        waitingAdd.Clear();

        foreach (KeyValuePair<int,SSAction> kv in actions){
            SSAction t_ac = kv.Value;
            if(t_ac.destroy)
            {
                waitingDelete.Add(t_ac.GetInstanceID());

            }
            else if(t_ac.enable)
            {
                t_ac.FixedUpdate();
            }
        }

        foreach (int key in waitingDelete){
            SSAction ac = actions[key];
            actions.Remove(key);
            DestroyObject(ac);
        }
        waitingDelete.Clear();
    }
    //新增一个动作,运行该动作
    public void RunAction(GameObject gameobject, SSAction  action, ISSActionCallback manager)
    {
        action.gameobject = gameobject;
        action.transform = gameobject.transform;
        action.callback = manager;
        waitingAdd.Add(action);
        action.Start();

    }
    //回调新的动作类型
    public void SSActionEvent(SSAction source, int param = 0,GameObject arrow = null)
    {
        //执行完飞行动作,开始颤抖动作。
        if(param == 1)
        {
            ArrowTrembleAction tremble = ArrowTrembleAction.GetSSAction();
            RunAction(arrow,tremble,this);
        }
        else{
            
        }
    }
}

5:ArrowShootActionManager,提供一个接口用于执行箭的射击动作;

该类继承了SSActionManager,提供了一个方法ArrowShoot,这个方法调用父类的RunAction方法执行射击动作。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
//
public class ArrowShootActionManager : SSActionManager
{
    //箭飞行的动作
    private ArrowShootAction shoot;                                
    public FirstSceneController scene_controller;              //当前场景的场景控制器
    
    protected void Start()
    {
        //设置firstscenecontroller的arrow——manager
        scene_controller = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController;
        scene_controller.arrow_manager = this;
    }

    //箭飞行
    public void ArrowShoot(GameObject arrow,Vector3 wind)
    {
        //实例化一个飞行动作。
        shoot = ArrowShootAction.GetSSAction(wind);
        //调用SSActionmanager的方法运行动作。
        this.RunAction(arrow, shoot, this);
    }
}

3.2 SSDirector 导演类

提供一个单实例,用来实现界面的统一管理,此游戏只需要一个界面。

public class SSDirector : System.Object
{
    private static SSDirector _instance;                  //导演类的实例
    public ISceneController CurrentScenceController { get; set; }
    public static SSDirector GetInstance()
    {
        if (_instance == null)
        {
            _instance = new SSDirector();
        }
        return _instance;
    }
}

3.3:Arrow Factory 工厂

用来管理箭的生产和回收,主要提供两个方法,GetArrow()和RecycleArrow()分别用来得到空闲的箭和回收重复利用使用过的箭:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ArrowFactory : MonoBehaviour
{
    //空闲队列
    private Queue<GameObject> FreeArrow = new Queue<GameObject>();
    //使用的箭的队列
    private List<GameObject> UsedArrow = new List<GameObject>();

   public FirstSceneController scenecontrolller;
   public GameObject arrow = null;


    public GameObject GetArrow(){
        if(FreeArrow.Count == 0){
            arrow = Instantiate(Resources.Load<GameObject>("Prefabs/arrow"));
        }
        else{
            arrow = FreeArrow.Dequeue();
            if(arrow.tag == "onTarget")//箭在靶子上
            {//使用动力学控制
                arrow.GetComponent<Rigidbody>().isKinematic = false;
                arrow.tag = "arrow";
                arrow.transform.GetChild(0).gameObject.SetActive(true);
            }
            arrow.gameObject.SetActive(true);
        }

        scenecontrolller = (FirstSceneController)SSDirector.GetInstance().CurrentScenceController;
        //得到弓箭上搭箭的位置                       
        Transform bow_mid = scenecontrolller.bow.transform.GetChild(0);
        //将箭的位置设置为弓中间的位置
        arrow.transform.position = bow_mid.transform.position;
        //箭随弓的位置变化
        arrow.transform.parent = scenecontrolller.bow.transform;
        UsedArrow.Add(arrow);
        return arrow;

    }


    public void RecycleArrow(GameObject arrow)
    {
        for(int i = 0 ; i < UsedArrow.Count; ++i)
        {
            if(arrow.GetInstanceID() == UsedArrow[i].gameObject.GetInstanceID())///
            {
                //UsedArrow[i].gameObject.SetActive(false);
                FreeArrow.Enqueue(UsedArrow[i]);
                UsedArrow.RemoveAt(i);
                break;
            }
        }
    }
}

3.4 相机脚本:

1:CameraFlow实现主相机跟随:

将相机的位置与弓的位置之间维持固定的位移偏差offset,即随着弓移动:

using UnityEngine;
using System.Collections;

public class CameraFlow : MonoBehaviour
{
    public GameObject bow;               //跟随的物体
    public float smothing = 5f;          //相机跟随的速度
    Vector3 offset;                      //相机与物体相对偏移位置

    void Start()
    {
        //设置相机与弓箭的位移差
        offset = new Vector3(2,0,-6);
    }

    void FixedUpdate()
    {
        //相机的目标位置
        Vector3 target = bow.transform.position + offset;
        //摄像机自身位置到目标位置平滑过渡
        transform.position = Vector3.Lerp(transform.position, target, smothing * Time.deltaTime);
    }
}

2:SecondeCamera:副相机脚本

在小窗口更加详细的展示箭的中靶位置和箭的颤抖动作,设置的展示时间为每次射击后的三秒钟:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;


public class SecondCamera : MonoBehaviour
{
    public float show_time = 3f;
    public bool show = false;

    void Update(){
        if(show == true){
            show_time -= Time.deltaTime;
            //展示时间超过3秒,停止展示
            if(show_time < 0){
                show = false;
                this.gameObject.SetActive(false);
            }
        }
    }
    //展示服相机
    public void ShowCamera()
    {
        show = true;
        this.gameObject.SetActive(true);
        show_time = 3f;
    }
}

3.5 RingController 环的脚本:

主要有一个成员变量RingScore用来标记当前环的分值,需要在Inspector窗口设置相应的分值;还有一个触发器检测,用来检查是否有箭击中该靶。如果有箭中靶,需要更改标签,以便调用相应的动作:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class RingController : MonoBehaviour
{
    //当前环的分值
    public  int RingScore = 0;

    public ISceneController scene;
    public ScoreRecorder sc_recorder;
    // Start is called before the first frame update
    void Start()
    {
        scene = SSDirector.GetInstance().CurrentScenceController as FirstSceneController;
        sc_recorder = Singleton<ScoreRecorder>.Instance;
    }

    // Update is called once per frame
    void Update()
    {
        
    }
   //碰撞检测,如果箭击中该环,就响应。
    void OnTriggerEnter(Collider arrow_head){
        //得到箭身
        Transform  arrow = arrow_head.gameObject.transform.parent;
        if(arrow == null)
        {
            return ;
        }
        //有箭中靶
        if(arrow.tag == "arrow"){
            //将箭的速度设为0
            arrow.GetComponent<Rigidbody>().velocity = new Vector3(0,0,0);
            //使用运动学运动控制
            arrow.GetComponent<Rigidbody>().isKinematic = true;
            //计分
            sc_recorder.RecordScore(RingScore);
            //将箭头设置为消失,
            arrow_head.gameObject.SetActive(false);/
            //标记箭为中靶
            arrow.tag = "onTarget";
        }
    }
}

3.6 Singleton 单实例:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
    protected static T instance;
    public static T Instance
    {
        get
        {
            if (instance == null) 
            {
                instance = (T)FindObjectOfType(typeof(T));
                if (instance == null)
                {
                    Debug.LogError("An instance of " + typeof(T)
                        + " is needed in the scene, but there is none.");
                }
            }
            return instance;
        }
    }
}

3.7 Interface接口:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface ISceneController{
    void LoadResources();
}

//箭的飞行和颤抖动作的回调接口
public interface ISSActionCallback
{
    void SSActionEvent(SSAction source,int param = 0, GameObject arrow = null);
}

3.8 玩家交互界面:

显示相应的信息,以及响应玩家的动作,如点击开始游戏的按钮,和通过方向键控制弓的移动,点击空格箭发射。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class UserGUI : MonoBehaviour
{
    //private IUserAction action;
    private FirstSceneController action;
    GUIStyle tip_style = new GUIStyle();
    GUIStyle wind_style = new GUIStyle();
    GUIStyle button_style = new GUIStyle();
    GUIStyle state_style = new GUIStyle();
    private int game_state = 0; //1-》游戏结束   0->游戏正在进行
    // Start is called before the first frame update
    void Start()
    {
       
       action = this.transform.gameObject.GetComponent<FirstSceneController>();
        tip_style.fontSize = 16;
        tip_style.normal.textColor = new Color(1,1,1,1);

        wind_style.normal.textColor = new Color(1,0.8f,0.8f,1);
        wind_style.fontSize = 16;

        button_style.normal.textColor = new Color(1,0.5f,0.7f,1);
        button_style.fontSize = 20;

        state_style.normal.textColor = new Color(0.9f,0.7f,0.5f,1);
        state_style.fontSize = 30;

        game_state = 0;//游戏状态 0->未开始  1-》开始
    }

    // Update is called once per frame
    //获取方向键的偏移量并移动弓
    void Update()
    {
        if(game_state == 1)
        {   
            //点击空格键射击
            if(Input.GetKeyDown(KeyCode.Space))
            {
                action.ShootArrow();
            }
            //获取方向键的位移
            float transY = Input.GetAxis("Vertical");
            float transX = Input.GetAxis("Horizontal");
             //移动弓相应的位移
            action.MoveBow(transX,transY); //
        }
    }
    //
    private void OnGUI()
    {//游戏已经开始
            if(game_state == 1)
            {   //游戏正在进行
                if(action.GetGameState() == 1) //
                {
                    //显示各种信息
                    GUI.Label(new Rect(20,10,150,50),"Score: ",button_style);
                    GUI.Label(new Rect(100,10,100,50),action.GetScore().ToString(),state_style);

                    GUI.Label(new Rect(200,10,100,50),"wind: ",wind_style);
                    Vector3 wind = action.GetWind();
                    string wind_data = "(" + wind.x +"," + wind.y + ",0)";
                    GUI.Label(new Rect(250,10,100,50),wind_data,wind_style);

                    
                    GUI.Label(new Rect(20,50,150,50),"ArrowNum: ",button_style);
                    GUI.Label(new Rect(120,50,100,50),action.GetArrowNum().ToString(),button_style);
                    //重新开始的按钮
                    if (GUI.Button(new Rect(80,100, 100, 50), "重新开始"))
                        {
                            action.Restart();
                        }
                }

                else{
                    GUI.Label(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 250, 100, 100), "游戏结束", button_style);
                }
        }
        //游戏未开始,检测开始按键
        else{
                GUI.Label(new Rect(Screen.width / 2 - 150, Screen.width / 2 - 370, 100, 100), "Bowman", wind_style);
                //开始按键如果按下,游戏开始
                if (GUI.Button(new Rect(Screen.width / 2 - 50, Screen.width / 2 - 150, 100, 50), "游戏开始"))
                {
                    game_state = 1;
                    action.BeginGame();
                }
                GUI.Label(new Rect(Screen.width / 2 - 100, Screen.width / 2 - 320, 400, 100), "使用方向键控制弓箭移动,点击空格键射箭", wind_style);
        }
    }
}

3.9 FirstSceneController 界面管理:

获取所需要的资源,响应玩家交互类传过来的动作,更新风向,分数等信息,调用箭的动作等等。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class FirstSceneController : MonoBehaviour,ISceneController
{
    //箭的动作管理者
    public ArrowShootActionManager arrow_manager;
    //箭的工厂
    public ArrowFactory factory;
    //副相机
    public Camera second_camera;
    //主相机
    public Camera main_camera;
    //记录分数
    public ScoreRecorder recorder;
    //弓
    public GameObject bow;
    //靶
    public GameObject target;
    //风
    public Vector3 wind;
    //箭
    public GameObject arrow;

    private  int arrow_num = 0;    //射出的箭数
    //箭的队列
    private List<GameObject> arrows = new List<GameObject>();
 
    private int gameState = 0; //0-游戏未开始,1-》游戏进行
    //加载箭和靶子
    public void LoadResources()
    {
        bow = Instantiate(Resources.Load("Prefabs/bow",typeof(GameObject))) as GameObject;
        target = Instantiate(Resources.Load("Prefabs/target",typeof(GameObject))) as GameObject;
    }
    //进行资源加载
    // Start is called before the first frame update
    void Start()
    {
        SSDirector director = SSDirector.GetInstance();
        factory = Singleton<ArrowFactory>.Instance;
        recorder = Singleton<ScoreRecorder>.Instance;
        director.CurrentScenceController = (ISceneController)this;
        arrow_manager = this.gameObject.AddComponent<ArrowShootActionManager>() as ArrowShootActionManager;
        LoadResources();
        main_camera.GetComponent<CameraFlow>().bow = bow;
        float windx = Random.Range(-2,2);
        float windy = Random.Range(-2,2);
        wind = new Vector3(windx,windy,0);
    }

    // Update is called once per frame
    void Update()
    {
        if(gameState == 1)
        {
            for(int i = 0; i < arrows.Count; ++i)
            {   //当前界面如果箭飞出靶外或者数量大于3则回收
                GameObject t_arrow = arrows[i];
                if(t_arrow.transform.position.z > 35 || arrows.Count > 3)
                {
                    factory.RecycleArrow(t_arrow);
                    arrows.RemoveAt(i);
                }
            }
        }
    }

    //通过方向键移动弓
    public void MoveBow(float transX, float transY)
    {   //限制弓的位置在一定范围内,x,y的绝对值小于5,z为-5;
        if(gameState == 1)
        {
              bow.transform.position = new Vector3(bow.transform.position.x,bow.transform.position.y,-5);
            if(bow.transform.position.x > 5)
            {
                bow.transform.position = new Vector3(5,bow.transform.position.y,bow.transform.position.z);
                return;
            }
            if(bow.transform.position.x < -5)
            {
                bow.transform.position = new Vector3(-5,bow.transform.position.y,bow.transform.position.z);
                return;
            }
            if(bow.transform.position.y > 5)
            {
                bow.transform.position = new Vector3(bow.transform.position.x,5,bow.transform.position.z);
                return;
            }
            if(bow.transform.position.y < -5)
            {
                bow.transform.position = new Vector3(bow.transform.position.x,-5,bow.transform.position.z);
                return;
            }
            //移动特定的距离
            bow.transform.Translate(new Vector3(0,transY,-transX)*Time.deltaTime);
        }
    }
    //射箭
    public void ShootArrow()
    {
        if(gameState == 1)
        {   //从工厂得到箭
            arrow = factory.GetArrow();
            arrows.Add(arrow);
            //通过管理类发射动作
            arrow_manager.ArrowShoot(arrow,wind);
            //开启副相机
            second_camera.GetComponent<SecondCamera>().ShowCamera();

        arrow_num += 1;
            //每次射击后风向改变;
        float windx = Random.Range(-2,2);
        float windy = Random.Range(-2,2);
        wind = new Vector3(windx,windy,0);
        }
    }
    //返回当前分数
    public int GetScore()
    {
        return recorder.score;
    }
    //得到风向
    public Vector3 GetWind()
    {
        return wind;
    }
    //得到射出的箭的数量
    public int GetArrowNum()
    {
        return arrow_num;
    }
    //重新开始游戏
    public void Restart()
    {
        arrow_num = 0;
        recorder.score = 0;
        for(int i = 0; i < arrows.Count; ++i){
            factory.RecycleArrow(arrows[i]);
        }
        arrows.Clear();
    }
    //开始游戏
    public void BeginGame()
    {
        gameState = 1;
    }
    //得到游戏的状态
    public int GetGameState()
    {
        return gameState;
    }
}

4:运行游戏:

4.1: 相机

需要添加一个新的相机作为副相机,需要设置副相机Viewport Rect属性,设置为小窗口显示:并添加脚本SecondCamera,同时主相机也需要添加脚本CameraFlow:
在这里插入图片描述

4.2 GameObject

创建一个空对象,并添加和设置相应脚本:
在这里插入图片描述

4.3: Target:

每个环都要添加RingController,并设置RingScore;

5: 游戏展示:

  • 一共有五环,分数分别为1,2,3,4,5;
  • 方向键移动弓的位置;
  • 空格箭发射

Unity3D射箭

6:项目地址:

项目gitee地址

点击Scenes/BowAndArrow可运行项目;

  • 0
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值