Unity——入门

1.生命周期函数

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

public class lesson1 : MonoBehaviour
{
    #region 了解帧的概念
    //Unity 底层已经帮助我们做好了死循环
    //我们需要学习Unity的生命周期函数
    //利用它做好的规则来执行我们的游戏逻辑就行了
    #endregion

    #region 生命周期函数的概念
    //所有继承 MonoBehaviour 的脚本 最终都会挂载到GameObject游戏对象上
    //生命周期函数 就是该脚本对象依附的GameObject对象从出生到消亡整个生命周期中
    //会通过反射自动调用的一些特殊函数

    //Unity帮助我们记录了一个GameObject对象依附了哪些脚本
    //会自动的得到这些对象,通过反射去执行一些固定名字的函数
    #endregion

    #region 生命周期函数
    //注意:
    //生命周期函数的访问修饰符一般为 private和 protected
    //因为不需要在外部自己调用生命周期函数 都是 Unity 自己帮助我们调用的

    //当对象(自己这个类对象) 被创建时 才会调用该生命周期函数
    private void Awake()
    {
        //在Unity中打印信息的两种方式

        //1.没有继承MonoBehaviour类的时候
        //Debug.Log("123");
        //Debug.LogError("出错了!!!");
        //Debug.LogWarning("警告!!!");

        //2.继承了MonoBehaviour 有一个线程的方法 可以使用
        print("Awake");
    }

    //对于我们来说 想要当一个对象被激活时 进行一些逻辑处理 就可以写在这个函数
    private void OnEnable()
    {
        print("OnEable");
    }

    //主要作用还是用于初始化信息的 但是它相对于Awake来说 要晚一点
    //因为它是在对象 进行第一次帧更新之前才会执行的
    private void Start()
    {
        print("Strart");
    }

    //它主要是用于 进行物理更新
    //它是每一帧的执行的 但是 这里的帧 和游戏帧 有点不同
    //它的时间间隔 是可以在 protect setting中的 Time里去设置的
    private void FixedUpdate()
    {
        print("FixedUpdate");
    }

    //主要用于处理游戏核心逻辑更新的函数
    private void Update()
    {
        print("Update");
    }

    //一般这个更新是用来处理 摄像机位置更新相关内容的
    //Update 和 LateUpdate 之间 Unity 进行了一些处理 处理我们动画相关的更新
    private void LateUpdate()
    {
        print("LateUpdate");
    }

    //如果我们希望在一个对象失活时做一些处理 就可以在该函数中写逻辑
    private void OnDisable()
    {
        print("OnDisable");
    }

    private void OnDestroy()
    {
        print("OnDestroy");
    }
    #endregion

    #region 生命周期函数 支持继承多态

    #endregion
}
//总结
//这些生命周期函数 如果你不打算在其中写逻辑 那就不要写出这些生命周期函数
//

2.inspector窗口可编辑的变量

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

public enum E_TestEnum
{
    Normal,
    Player,
    Monster
}
[System.Serializable]
public struct MyStruct
{
    public int age;
    public bool sex;
}
[System.Serializable]
public class MyClass
{
    public int age;
    public bool sex;
}
public class lesson2 : MonoBehaviour
{
    #region Inspector显示的可编辑内容就是脚本的成员变量

    #endregion

    #region 私有和保护无法显示编辑
    private int i1;
    protected string str1;
    #endregion

    #region 让私有的和保护的也可以被显示
    //加上强制序列化字段特性
    //[SerializeField]
    //所谓序列化就是把一个对象保存到一个文件或数据库字段中去

    [SerializeField]
    private int privateInt;
    [SerializeField]
    protected string protectedStr;
    #endregion

    #region 公共的可以显示编辑
    [HideInInspector]
    public int publicInt = 10;
    public bool publicBool = false;
    #endregion

    #region 公共的也不让其显示编辑
    //在变量前加上特性
    //[HideInInspector]

    [HideInInspector]
    public int publicInt2 = 50;
    #endregion

    #region 大部分类型都能显示编辑
    public int[] array;
    public List<int> list;
    public E_TestEnum type;
    public GameObject gameObj;

    //字典不能被Inspector窗口显示
    public Dictionary<int, string> dic;
    //自定义类型变量
    public MyStruct myStruct;
    public MyClass myClass;
    #endregion

    #region 让自定义类型可以被访问
    //加上序列化特性
    //[System.Serializable]
    //字典怎样都不行
    #endregion

    #region 一些辅助特性
    //1.分组说明特性 Header
    //为成员分组
    //Header 特性
    //[Header("分组说明")]

    [Header("基础属性")]
    public int age;
    public bool sex;
    [Header("战斗属性")]
    public int atk;
    public int def;

    //2.悬停注释 Tooltip
    //为变量添加说明
    //[Tooltip("说明内容")]

    [Tooltip("闪避")]
    public int miss;

    //3.间隔特性 Spcae()
    //让两个字段间出现间隔
    //[Space()]

    [Space()]
    public int crit;

    //4.修饰数值的滑条范围 Range
    //[Range(最大值,最小值)]

    [Range(0,10)]
    public float luck;

    //5.多行显示字符串 默认不写参数显示3行
    //写参数就是对应行
    //[Multiline(4)]

    [Multiline()]
    public string tips;

    //6.滚动条显示字符串 
    //默认不写参数就是超过三行显示滚动条
    //[TextArea(3,4)]    
    //最少显示3行,最多4行,超过四行就显示滚动条

    [TextArea(3, 4)]
    public string MyLife;

    //7.为变量添加快捷方法 ContextMenuItem
    //参数1 显示按钮名
    //参数2 方法名 不能有参数
    //[ContestMenuItem("显示按钮名","方法名")]

    [ContextMenuItem("重置钱", "Test")]
    public int money;
    private void Test()
    {
        money = 0;
    }

    //8.为方法添加特性能够在Inspector中执行
    //[ContextMenu("测试函数")]

    [ContextMenu("哈哈哈")]
    private void TestFun()
    {
        print("测试方法");
    }
    
    #endregion

    #region 注意
    //1.Inspector窗口中的变量关联的就是对象的成员变量,运行时改变他们就是在改成员变量
    //2.拖曳到GameObject对象后 再改变脚本中变量默认值 界面上不会改变
    //3.运行中修改的信息不会保存
    #endregion

    private void Start()
    {
        print(privateInt);
        print(protectedStr);
    }
}

3.Mono中的重要内容

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

public class lesson3 : MonoBehaviour
{
    public lesson3 otherLesson3;
    // Start is called before the first frame update
    void Start()
    {
        #region 重要成员
        //1.获取依附的GameObject
        print(this.gameObject.name);

        //2.获取依附的GameObject的位置信息
        //得到对象位置信息
        print(this.transform.position);   //位置
        print(this.transform.eulerAngles);//角度
        print(this.transform.lossyScale); //缩放大小
        //这种写法和上面都是一样的效果 都是得到依附的对象的位置信息
        //this.gameObject.transform

        //3.获取脚本是否激活
        this.enabled = true; //激活
        this.enabled = false;//失活

        //获取别的脚本对象 衣服的gameObject和transform位置信息
        print(otherLesson3.gameObject.name);
        print(otherLesson3.transform.position);
        #endregion

        #region 重要方法
        //得到依附对象上挂载的其他脚本

        //1.得到自己挂载的单个脚本
        //根据脚本名获取
        lesson3_Test t=this.GetComponent("lesson3_Test") as lesson3_Test;
        print(t);

        //根据Type获取
        t = this.GetComponent(typeof(lesson3_Test)) as lesson3_Test;
        print(t);

        //根据泛型获取 建议使用泛型获取 因为不用二次转换
        t = this.GetComponent<lesson3_Test>();
        if (t != null)
        {
            print(t);
        }
        //只要你能够得到场景中别的对象或者对象依附的脚本
        //那你就可以获取到它的所有信息

        //2.得到自己挂载的多个脚本
        lesson3[] array = this.GetComponents<lesson3>();
        print(array.Length);
        List<lesson3> list = new List<lesson3>();
        this.GetComponents<lesson3>(list);
        print(list.Count);

        //3.得到子对象挂载的脚本(它默认也会找自己身上是否挂载该脚本)
        //函数是有一个参数的 默认不传 是false
        //意思就是 如果子对象失活 是不会去找这个对象上是否有某个脚本
        //如果传 true 即使失活 也会找
        t = this.GetComponentInChildren<lesson3_Test>();
        print(t);
        //得子对象 挂载脚本 多个
        lesson3_Test[] lts = this.GetComponentsInChildren<lesson3_Test>(true);
        print(lts.Length);

        List<lesson3_Test> list2 = new List<lesson3_Test>();
        this.GetComponentsInChildren<lesson3_Test>(true, list2);

        //4.得到父对象挂载的脚本(它默认也会找自己身上是否挂载了该脚本)
        t = this.GetComponentInParent<lesson3_Test>();
        print(t);
        lts = this.GetComponentsInParent<lesson3_Test>();
        print(lts.Length);
        //它也有list的 和上面相同的套路

        //5.尝试获取脚本
        lesson3_Test l3t;
        //提供了一个更加安全的 获取单个脚本的方法 如果得到了 会返回true
        //然后再来进行逻辑处理即可
        if (this.TryGetComponent<lesson3_Test>(out l3t))
        {
          //逻辑处理  
        }
        #endregion
    }

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

4.最小单位GameObject

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

public class lesson4 : MonoBehaviour
{
    //准备用来克隆的对象
    //1.直接是场景上的某个对象
    //2.可以是一个预设体对象
    public GameObject myObj;

    public GameObject myObj2;

    // Start is called before the first frame update
    void Start()
    {
        #region GameObject中的成员变量
        //名字
        print(this.gameObject.name);
        this.gameObject.name = "Lesson4改名";
        print(this.gameObject.name);
        //是否激活
        print(this.gameObject.activeSelf);
        //是否是静态
        print(this.gameObject.isStatic);
        //层级
        print(this.gameObject.layer);
        //标签
        print(this.gameObject.tag);
        //transform
        //this.transform 上一节课讲解的 通过Mono去得到的依附对象的GameObject的位置信息
        //他们得到的信息是一样的 都是依附的GameObject的位置信息
        print(this.gameObject.transform.position);
        #endregion

        #region GameObject中的静态方法
        //创建自带几何体
        //只要得到了一个GmeObject对象 我就可以得到它身上挂载的任何脚本信息
        //通过obj.GetComponent来得取
        GameObject obj=GameObject.CreatePrimitive(PrimitiveType.Cube);
        obj.name = "立方体";

        //查找对象相关的知识
        //两种找单个对象的相同点
        //1.无法找到失活的对象
        //  只能找到激活的对象
        //2.如果场景中 存在多个满足条件的对象
        //  我们无法准确确定找到的是谁

        //1.查找单个对象

        //通过对象名查找
        //这个查找效率比较低下 因为他会在场景中的所有对象去查找
        //没有找到 就会返回null
        GameObject obj2 = GameObject.Find("立方体");
        if (obj2 != null)
        {
            print(obj2.name);
        }

        //通过tag来查找对象
        //GameObject obj3 = GameObject.FindWithTag("Player");
        //该方法和上面这个方法 效果一样 只是名字不一样而已
        GameObject obj3 = GameObject.FindGameObjectWithTag("Player");
        if (obj3 != null)
        {
            print("根据tag找的对象" + obj3.name);
        }
        else 
        {
            print("根据tag没有找到对应对象");
        }

        //得到某一个单个对象 目前有两种方式了
        //1.是 public 从外部面板拖 进行关联
        //2.通过API去找

        //2.查找多个对象
        //找多个对象的API 只能是通过tag去找多个 通过名字 是没有找多个的方法的

        //通过tag找到多个对象
        //它也是 只能找到 激活对象 无法找到失活对象
        GameObject[] objs = GameObject.FindGameObjectsWithTag("Player");
        print("找到tag为Player对象的个数" + objs.Length);

        //还有几个查找对象相关是用的比较少的方法 是GameObject父类 Object提供的方法
        //引出额外知识点 Unity中的Object和C#中的万物之父的区别
        //Unity里面的Object 不是指的万物之父object
        //Unity里的Object 命名空间在UnityEngine中的 Object类 也是集成万物之父的一个自定义类
        //C#中的Object 命名空间是在System中的

        //它可以找到场景中挂载的某一个脚本对象
        //效率更低 上面的GameObject.Find和通过FindWithTag找 只是遍历对象
        //这个方法 不仅要遍历对象 还要遍历对象上挂载的脚本
        lesson4 o= GameObject.FindObjectOfType<lesson4>();
        print(o.gameObject.name);

        //实例化对象(克隆对象)的方法
        //实例化(克隆)对象 它的作用 是根据一个GameObject对象 创建出一个和它一模一样的对象
        GameObject obj5= GameObject.Instantiate(myObj);
        //以后学了更多知识点 就可以在这操作obj5
        //如果你继承了 MonoBehavior 其实可以不用写GameObject一样可以使用
        //因为 这个方法是Unity里面的 Object基类提供给我们的 所有可以直接使用
        //Instantiate(myObj);

        //删除对象的方法
        GameObject.Destroy(myObj2);
        //第二个参数 代表延迟多少秒就会删除
        GameObject.Destroy(obj5, 5);
        //Destroy 不仅可以删除对象 还可以删除脚本
        //GameObject.Destroy(this);

        //删除对象有两种作用
        //1.是删除指定的一个游戏对象
        //2.是删除一个指定的脚本对象
        //注意:这个Destroy方法 不会马上移除对象 只是给这个对象jail一个移除标识
        //     一般情况下 它会在下一帧时吧这个对象移除并从内存中移除

        //如果没有特殊需求 就是一定要马上移除一个对象的话
        //建议使用上面的 Destroy方法 因为是异步的 降低卡顿的几率
        //下面这个方法 就是立即把对象 从内存中移除了
        GameObject.DestroyImmediate(myObj);

        //如果是继承MonoBehavior的类 不用写GameObject
        //Destroy(myObj);
        //DestroyImmediate(myObj);

        //过场景不移除
        //默认情况 在切换场景时 场景中对象都会被自动删除掉
        //如果你希望某个对象 过场景不被移除
        //下面这句代码 就是不想谁过场景被移除 就传谁
        //一般都是传 依附的GameObject对
        //比如下面这句代码的意思 就是自己依附的GameObject对象 过场景不被删除
        GameObject.DontDestroyOnLoad(this.gameObject);
        //如果继承MonoBehavior也可以直接写
        //DontDestroyOnLoad(this.gameObject);
        #endregion

        #region GameObject中的成员方法
        //创建空物体
        //new一个GameObject就是在创建一个空物体
        GameObject obj6 = new GameObject();
        GameObject obj7 = new GameObject("空物体");
        GameObject obj8 = new GameObject("加脚本的空物体",typeof(lesson2),typeof(lesson1));

        //为对象添加脚本
        //继承MonoBehavior的脚本 是不能够去new
        //如果想要动态的添加继承MonoBehavior的脚本 在某一个对象上
        //直接使用GameObject提供的方法即可
        lesson1 lesson1 = obj6.AddComponent(typeof(lesson1)) as lesson1;
        //用泛型更方便
        lesson2 lesson2 = obj6.AddComponent<lesson2>();
        //通过返回值 可以得到加入的脚本信息
        //来进行一些处理

        //得到脚本的成员方法 和继承Mono的类得到脚本的方法一模一样

        //标签比较
        //下面两种比较的方法 是一样的
        if (this.gameObject.CompareTag("Player"))
        {
            print("对象的标签是Player");
        }
        if (this.gameObject.tag == "Player")
        {
            print("对象的标签是Player");
        }

        //设置激活失活
        //false失活
        //true激活
        obj6.SetActive(false);
        obj7.SetActive(false);
        obj8.SetActive(false);

        //次要的成员方法 了解即可 不建议使用
        //强调
        //下面讲的方法 都不建议大家使用 效率比较低
        //通过广播或者发送消息的形式 让自己或者别人 执行某些行为方法

        //通知自己 执行上面行为
        //命令自己 去执行这个TestFun这个函数 会在自己身上挂载的所有脚本去找这个名字的函数
        //它会去找到 自己身上所有的脚本 有这个名字的函数去执行
        this.gameObject.SendMessage("TestFun");
        this.gameObject.SendMessage("TestFun2",199);

        //广播行为 让自己和自己的子对象执行
        //this.gameObject.BroadcastMessage("函数名");

        //向父对象和自己发送消息 并执行
        //this.gameObject.SendMessageUpwards("函数名");
        #endregion
    }
    void TestFun()
    {
        print("Lesson4d TestFun");
    }
    void TestFun2(int a)
    {
        print("Lesson4的TestFun2" + a);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    //总结
    //GameObject的常用内容
    
    //基本成员变量
    //名字 失活激活状态 标签 层级 等等
    
    //静态方法相关
    //创建自带几何体
    //查找场景中对象
    //实例化对象
    //删除对象
    //过场景不移除
    
    //成员方法
    //为对象 动态添加指定脚本
    //设置失活激活的状态
    //和MonoBehavior中相同的 得到脚本相关的方法
}

5.时间相关Time

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

public class lesson5 : MonoBehaviour
{
    void Update()
    {
        #region Time相关内容主要用来干啥
        //时间相关内容 主要 用于游戏中参与唯一、计时、时间暂停等
        #endregion

        #region 时间缩放比例
        //时间停止
        //Time.timeScale = 0;
        //恢复正常
        //Time.timeScale = 1;
        //2倍速
        //Time.timeScale = 2;
        #endregion

        #region 帧间隔时间
        //帧间隔时间 主要是用来计算位移
        //路程=时间*速度
        //根据需求选择参与计算的间隔时间
        //如果希望 游戏暂停时就不动的 那就使用 deltaTime
        //如果希望 不受暂停影响 unscaledDeltaTime

        //帧间隔时间:最近的一帧 用了多少时间(秒)
        //受scale影响
        print("帧间隔时间:"+Time.deltaTime);
        //不受scale影响的帧间隔时间
        print("不受scale影响的帧间隔时间:" + Time.unscaledDeltaTime);
        #endregion

        #region 游戏开始到现在的时间
        //它主要用来计时 单机游戏中计时
        //受scale影响
        print("游戏开始到现在的时间"+Time.time);
        //不受scale影响
        print("不受scale影响的游戏开始到现在的时间" + Time.unscaledTime);
        #endregion

        #region 帧数
        //从开始到现在游戏跑了多少帧(次循环)
        print(Time.frameCount);
        #endregion
    }
    private void FixedUpdate()
    {
        #region 物理帧间隔时间 FixedUpdate
        //受scale影响
        print(Time.fixedDeltaTime);
        //不受scale影响
        print(Time.fixedUnscaledDeltaTime);
        #endregion
    }
    //总结
    //Time相关的内容
    //最常用的 就是我们的
    //1.帧间隔时间     就用来计算位移相关内容
    //2.时间缩放比例   用来暂停 或者倍速等等
    //3.帧数(帧同步)
}

6.位置和位移

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

public class lesson6 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        #region Transform主要用来干嘛?
        //游戏对象(GameObject)位移、旋转、缩放、父子关系、坐标转换等相关操作都由它处理
        //它是Unity提供的极其重要的类
        #endregion

        #region 必备知识点 Vector3基础
        //Vector3主要是用来表示三维坐标系中的 一个点 或者一个向量
        //申明
        Vector3 v = new Vector3();
        v.x = 10;
        v.y = 10;
        v.z = 10;
        //只传x y 默认z是0
        Vector3 v2 = new Vector3(10, 10);
        //一步到位
        Vector3 v3 = new Vector3(10, 10, 10);

        Vector3 v4;
        v4.x = 10;
        v4.y = 10;
        v4.z = 10;

        //Vector3的基本运算
        //+ - * /
        Vector3 v1 = new Vector3(1, 1, 1);
        Vector3 v12 = new Vector3(2, 2, 2);
        //对应用x+或者-x……
        print(v1 + v12);
        print(v1 - v12);

        print(v1 * 10);
        print(v12 / 2);

        //常用
        print(Vector3.zero);//0,0,0
        print(Vector3.right);//1,0,0
        print(Vector3.left);//-1,0,0
        print(Vector3.forward);//0,0,1
        print(Vector3.back);//0,0,-1
        print(Vector3.up);//0,1,0
        print(Vector3.down);//0,-1,0

        //常用的一个方法
        //计算两个点之间的距离的方法
        print(Vector3.Distance(v1, v12));
        #endregion

        #region 位置
        //相对世界坐标系
        //this.gameObject.transform
        //通过position得到的位置 是相对于 世界坐标系的 原点的位置
        //可能和面板上显示的 是不一样的
        //因为如果对象有父子关系 并且父对象位置 不在原点 那么 和面板上肯定就是不一样的
        print(this.transform.position);

        //相对父对象
        //这两个坐标 对于我们来说 很重要 如果你想以面板坐标为准来进行坐标设置
        //那一定是通过LocalPosition来进行设置的
        print(this.transform.localPosition);

        //他们两个 可能出现是一样的情况
        //1.父对象的坐标 就是世界坐标系原点0,0,0
        //2.对象没有父对象

        //注意:位置的赋值不能直接改变x,y,z 只能整体改变
        //不能单独改x y z某一个值
        this.transform.position = new Vector3(10, 10, 10);
        this.transform.localPosition = Vector3.up * 10;
        //如果只想改一个值 其他值要保持原有坐标一致
        //1.直接赋值
        this.transform.position = new Vector3(19, this.transform.position.y, this.transform.position.z);
        //2.先取出来 再赋值
        Vector3 vPos = this.transform.localPosition;
        vPos.x = 10;
        this.transform.localPosition = vPos;

        //如果你想得到对象当前的 一个朝向
        //那么就是通过 Transform .出来的
        //对象当前的各朝向
        //对象当前的面朝向
        print(this.transform.forward);
        //对象当前的头顶朝向
        print(this.transform.up);
        //对象当前的右手边
        print(this.transform.right);
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        #region 位移
        //理解坐标系下的位移计算公式
        //路程 = 方向*速度*时间

        //方式一 自己计算
        //想要变化的 就是 position
        //用当前的位置 + 我要动多长距离 得出最终所在的位置
        //this.transform.position = this.transform.position + this.transform.forward * 1 * Time.deltaTime;
        //因为我用的是 this.transform.forward 所以他始终会朝向相对于自己的面朝向去动
        //this.transform.position+= this.transform.forward * 1 * Time.deltaTime;
        //方向非常重要 因为 它决定了你的前进方向
        //this.transform.position += Vector3.forward * 1 * Time.deltaTime;

        //方式二 API
        //参数一:表示位移多少     路程 = 方向*速度*时间
        //参数二:表示 相对坐标系  默认 该参数 是相对于自己坐标系的

        //1.相对于世界坐标系的 z轴 动 始终是朝 世界坐标系的 z轴正方向移动
        //this.transform.Translate(Vector3.forward * 1 * Time.deltaTime,Space.World);

        //2.相对于世界坐标的 自己的面朝向去动 始终朝自己的面朝向移动
        //this.transform.Translate(this.transform.forward * 1 * Time.deltaTime, Space.World);

        //3.相对于自己的坐标系 下的 自己的面朝向向量移动(一定不会这样让物体移动)XXXXXXX
        //this.transform.Translate(this.transform.forward * 1 * Time.deltaTime, Space.Self);

        //4.相对于自己的坐标系 下的 z轴正方向移动 始终朝自己的面朝向移动
        //this.transform.Translate(Vector3.forward * 1 * Time.deltaTime, Space.Self);

        //注意:一般使用API来进行位移
        #endregion
    }
    //总结
    //Vector3
    //如何申明 提供的 常用静态属性 和一个 计算距离的方法
    //位置
    //相对于世界坐标系 和 相对于父对象 这两个坐标的区别
    //不能够 单独修改 xyz 只能一起统一改
    //位移
    //自己如何计算来进行位移
    //API是哪个方法 来进行位移 使用时要注意
}

7.角度和旋转

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

public class lesson7 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        #region 角度相关
        //相对世界坐标角度
        print(this.transform.eulerAngles);//欧拉角

        //相对父对象角度
        print(this.transform.localEulerAngles);

        //注意:设置角度和设置位置一样 不能单独设置xyz 要一起设置
        //this.transform.localEulerAngles = new Vector3(10, 10, 10);
        //如果我们希望改变的 角度 是面板上显示的内容 那一点是改变 相对父对象的角度
        //this.transform.eulerAngles = new Vector3(10, 10, 10);
        print(this.transform.localEulerAngles);
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        #region 旋转相关
        //自己计算(省略不讲了 和位置一样 不停改变角度即可)

        //API计算
        //自转
        //第一个参数 相当于是旋转的角度 每一帧
        //第二个参数 默认不填 就会相对于自己坐标系 进行旋转
        //this.transform.Rotate(new Vector3(0, 10, 0) * Time.deltaTime);
        //this.transform.Rotate(new Vector3(0, 10, 0) * Time.deltaTime,Space.World);

        //相对于某个轴 转多少度
        //参数一:是相对哪个轴进行转动
        //参数二:是转动的 角度 是多少
        //参数三:默认不填 就是相对于自己的坐标系 进行旋转
        //        如果填 可以填相对于 世界坐标系进行旋转
        //this.transform.Rotate(Vector3.up, 10 * Time.deltaTime);
        //this.transform.Rotate(Vector3.up, 10 * Time.deltaTime,Space.World);

        //相对于某一个点转
        //参数一:相当于哪一个点 转圈圈
        //参数二:相对于哪一个点 的哪一个轴转圈圈
        //参数三:转的度数 旋转速度*时间
        this.transform.RotateAround(Vector3.zero, Vector3.up, 10 * Time.deltaTime);
        #endregion
    }
    //总结
    //角度相关和位置相关 差不多
    //如何得到角度
    //通过transform 可以得到相对于世界坐标系和相对于父对象的
    //如何自传和绕着别人转
    //Rotate
    //RotateAround
}

8.缩放和看向

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

public class lesson8 : MonoBehaviour
{
    public Transform lookAtObj;

    // Start is called before the first frame update
    void Start()
    {
        #region 缩放
        //相对世界坐标系
        print(this.transform.lossyScale);
        //相对本地坐标系(父对象)
        print(this.transform.localScale);

        //注意:
        //1.同样缩放不能只改xyz 只能一起改
        //所以 我们一般要修改缩放大小 都是改的相对于父对象的缩放大小 localScale
        //this.transform.localScale = new Vector3(3, 3, 3);
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        //2.Unity没有提供关于缩放的API
        //之前的 旋转 位移 都提供了相应的 API 但是缩放没有
        //如果你想要 让缩放发生变化 只能自己去写(自己算)

        //this.transform.localScale += Vector3.one * Time.deltaTime;

        #region 看向
        //让一个对象的面朝向 可以一直看向某一个点或者某一个对象
        //看向一个点 相对于世界坐标系的
        //this.transform.LookAt(Vector3.zero);
        //看向一个对象 就传入一个对象的 Transform信息
        this.transform.LookAt(lookAtObj);
        #endregion
    }
    //总结
    
    //缩放相关
    //相对于 世界坐标系的缩放 只能得 不能改
    //只能去修改相对于本地坐标系的缩放(相对于父对象)
    //没有提供相对应的API来 缩放变化 只能自己算
    
    //看向
    //LookAt 看向一个点 或者一个对象
    //一定记住 是写在Update里面 才会不停变化
}

9.父子关系

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

public class lesson9 : MonoBehaviour
{
    public Transform son;

    // Start is called before the first frame update
    void Start()
    {
        #region 获取和设置父对象
        //获取父对象
        //print(this.transform.parent.name);
        //设置父对象 断绝父子关系
        //this.transform.parent = null;
        //设置父对象 认父亲
        //this.transform.parent = GameObject.Find("Father2").transform;

        //通过API来进行父子关系的设置
        //this.transform.SetParent(null);//断绝父子关系
        //this.transform.SetParent(GameObject.Find("Fther2").transform);//认父亲

        //参数一:父亲
        //参数二:是否保留世界坐标的 位置 角度 缩放 信息
        //        true  会保留 世界坐标下的状态 和 父对象 进行计算 得到本地坐标系的信息
        //        false 不会保留 会直接吧世界坐标系下的 位置角度缩放 直接赋值到 本地坐标系下
        //this.transform.SetParent(GameObject.Find("Fther3").transform,true);
        #endregion

        #region 抛妻弃子
        //就是和自己所有的儿子 断绝关系 没有父子关系了
        //只断绝自己和儿子的关系 儿子和孙子的关系不变
        this.transform.DetachChildren();
        #endregion

        #region 获取子对象
        //按名字查找儿子
        //找到儿子的 transform信息
        //Find方法 是能够找到 失活的对象的!!!GameObject相关的 查找 是不能找到失活对象的
        print(this.transform.Find("Cube(1)").name);
        //它只能找到自己的儿子 找不到自己的孙子!!!
        print(this.transform.Find("GameObject").name);
        //虽然它的效率 比GameObject.Find相关 要高一些 但是 前提是你必须知道父亲是谁 才能找到

        //遍历儿子
        //如何得到有多少个儿子
        //1.失活的儿子也会算数量
        //2.找不到孙子 所有孙子不会算数量
        print(this.transform.childCount);
        //通过索引号 去得到自己对应的儿子
        //如果编号 超出了儿子的数量的范围 那会直接报错的
        //返回值 是 transform 可以得到对应儿子的 位置相关信息
        this.transform.GetChild(0);

        for (int i = 0; i < this.transform.childCount; i++)
        {
            print("儿子的名字" + this.transform.GetChild(i).name);
        }
        #endregion

        #region 儿子的操作
        //判断自己的爸爸是谁
        //一个对象 判断自己是不是另一个对象的儿子
        if (son.IsChildOf(this.transform))
        {
            print("是我的儿子");
        }
        //得到自己作为儿子的编号
        print(son.GetSiblingIndex());
        //把自己设置为第一个儿子
        son.SetAsFirstSibling();
        //把自己设置为最后一个儿子
        son.SetAsLastSibling();
        //把自己设置为指定个儿子
        //就算填的数量 超出了范围(负数或者更大的数) 不会报错 会直接设置成最后一个编号
        son.SetSiblingIndex(1);
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    //总结
    //设置父对象相关的内容
    //获取子对象
    
    //抛妻弃子
    //儿子的操作
}

10.坐标转换

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

public class lesson10 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        #region 世界坐标转本地坐标
        print(Vector3.forward);
        
        //世界坐标系 转本地坐标系 可以帮助我们大概判断一个相对位置
        
        //世界坐标系的点 转换为 相对本地坐标系的点
        //受到缩放影响
        print("转换后的点"+ this.transform.InverseTransformPoint(Vector3.forward));

        //世界坐标系的方向 转换为 相对本地坐标系的方向
        //不受缩放影响
        print("转换后的方向" +this.transform.InverseTransformDirection(Vector3.forward));

        //受缩放影响
        print("转换后的方向(受缩放影响)" + this.transform.InverseTransformVector(Vector3.forward));
        #endregion

        #region 本地坐标转世界坐标
        //本地坐标系的点 转换为 相对世界坐标系的点 受到缩放影响
        print("本地 转 世界 点"+this.transform.TransformPoint(Vector3.forward));

        //本地坐标系的方向 转换为 相对世界坐标系的方向
        //不受缩放影响
        print("本地 转 世界 点" + this.transform.TransformDirection(Vector3.forward));

        //受缩放影响
        print("本地 转 世界 点" + this.transform.TransformVector(Vector3.forward));
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    //总结
    //其中最重要的 就算 本地坐标系的点 转世界坐标系的点
    //比如 现在玩家要在自己面前的n个单位前 放一团火 这时候 我不用关心世界坐标系
    //通过 相对于本地坐标系的位置 转换为 世界坐标系的点 进行 特效的创建 或者 攻击范围的判断
}

11.输入相关Input

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

public class lesson11 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        #region 注意:输入相关内容肯定是写在Update中的

        #endregion

        #region 鼠标在屏幕位置
        //屏幕坐标的原点 是在 屏幕的左下角 往右是x轴正方向 往上是y轴正方向
        //返回值是 Vector3 但是只有 x和y有值 z一直是0 是因为屏幕本来就是2D的  不存在z轴
        //print(Input.mousePosition);
        #endregion

        #region 检测鼠标输入
        //鼠标按下相关检测 对于我们来说
        //比如:1.可以做 发射子弹
        //      2.可以控制摄像机 转动

        //鼠标按下一瞬间 进入
        //0左键 1右键 2中键
        //只有按下的这一瞬间 进入一次
        //if (Input.GetMouseButtonDown(0))
        //{
        //    print("鼠标左键按下了");
        //}

        //鼠标抬起一瞬间 进入
        //if (Input.GetMouseButtonUp(0))
        //{
        //    print("鼠标左键抬起了");

        //}

        //鼠标长按按下抬起都会进入
        //当按住按键不放时 会一直进入 这个判断
        //if (Input.GetMouseButton(1))
        //{
        //    print("右键按下");
        //}

        //中键滚动
        //返回值的 y  -1往下滚  0没有滚  1往上滚
        //它的返回值 是Vector的值 我们鼠标中键滚动 会改变其中的y值
        //print(Input.mouseScrollDelta);
        #endregion

        #region 检测键盘输入
        //比如说 按一个键释放一个技能或者切换武器 等等的操作

        //键盘按下
        //if (Input.GetKeyDown(KeyCode.W))
        //{
        //    print("W键按下");
        //}

        //传入字符串的重载
        //这里传入的 字符串 不能是大写的 不然会报错
        //if (Input.GetKeyDown("q"))
        //{
        //    print("Q键按下");
        //}

        //键盘抬起
        //if (Input.GetKeyUp(KeyCode.W))
        //{
        //    print("W键抬起");
        //}

        //键盘长按
        //if (Input.GetKey(KeyCode.W))
        //{
        //    print("长按W");
        //}
        #endregion

        #region 检测默认轴输入
        //我们学习鼠标 键盘输入 主要是用来
        //控制玩家 比如 旋转 位移等等
        //所以Unity提供了 更方便的方法 来帮助我们控制 对象的 位移和旋转

        //键盘AD按下时   返回 -1到1之间的变换
        //得到这个值 就是我们的 左右方向 我们可以通过它来控制 对象左右移动 或者左右旋转
        //print(Input.GetAxis("Horizontal"));

        //键盘SW按下时   返回 -1到1之间的变换
        //得到这个值 就是我们的 上下方向 我们可以通过它来控制 对象上下移动 或者上下旋转
        //print(Input.GetAxis("Vertical"));

        //鼠标横向移动时 返回 -1到1之间的变换
        //print(Input.GetAxis("Mouse X"));

        //鼠标纵向移动时 返回 -1到1之间的变换
        //print(Input.GetAxis("Mouse Y"));
        
        //我们默认的 GetAxis方法 是有渐变的 会从 -1到1之间 渐变 会出现小数

        // GetAxisRaw方法 和 GetAxis 使用方式相同
        //只不过 它的返回值 只会是 -1 0 1 不会有中间值
        #endregion

        #region 其他
        //是否有任意键或鼠标长按
        if (Input.anyKey)
        {
            print("有一个键 长按");
        }

        //是否有任意键或鼠标按下
        if (Input.anyKeyDown)
        {
            print("有一个键 按下");
            //这一帧的键盘输入
            print(Input.inputString);
        }

        //手柄输入相关
        //得到连接的手柄的所有按钮名字
        string[] strs = Input.GetJoystickNames();

        //某一个手柄键按下
        if (Input.GetButtonDown("Jump"))
        {
            
        }

        //某一个手柄键抬起
        if (Input.GetButtonUp("Jump"))
        {

        }

        //某一个手柄键长按
        if (Input.GetButton("Jump"))
        {

        }

        //移动设备触摸相关
        if (Input.touchCount > 0)
        {
            Touch t1 = Input.touches[0];
            //位置
            print(t1.position);
            //相对上次位置的变化
            print(t1.deltaPosition);
        }

        //是否启用多点触控
        Input.multiTouchEnabled = false;

        //陀螺仪(重力感应)
        //是否开启陀螺仪 必须开启 才能正常使用
        Input.gyro.enabled = true;

        //重力加速度向量
        print(Input.gyro.gravity);

        //旋转速度
        print(Input.gyro.rotationRate);

        //陀螺仪 当前的旋转四元数
        //比如 用这个角度信息 来控制 场景上的一个3D物体受到重力影响
        //手机怎么动 它怎么动
        print(Input.gyro.attitude);
        #endregion
    }
    //总结
    //Input类 提供大部分和输入相关的内容
    //鼠标、键盘、触屏、手柄、重力感应
    //对于我们目前来说
    //鼠标、键盘 是必须掌握的核心知识
    //今天必须记住 鼠标键盘输入相关的API
    //GetAxis
}

12.屏幕相关Screen

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

public class lesson12 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        #region 静态属性

        #region 常用
        //当前屏幕分辨率
        Resolution r = Screen.currentResolution;
        print("当前屏幕分辨率的宽" + r.width + "高" + r.height);

        //屏幕窗口当前宽高
        //这里得到的 是当前 窗口的 宽高 不是设备分辨率的宽高
        //一般写代码 是要用窗口的宽高 做计算时 就用他们
        print(Screen.width);
        print(Screen.height);

        //屏幕休眠模式
        Screen.sleepTimeout = SleepTimeout.NeverSleep;
        #endregion

        #region 不常用
        //运行时是否全屏模式
        //Screen.fullScreen = true;

        //窗口模式
        //独占全屏    Screen.fullScreenMode = FullScreenMode.ExclusiveFullScreen;
        //全屏窗口    Screen.fullScreenMode = FullScreenMode.FullScreenWindow;
        //最大化窗口  Screen.fullScreenMode = FullScreenMode.MaximizedWindow;
        //窗口模式    Screen.fullScreenMode = FullScreenMode.Windowed;

        //移动设备屏幕转向相关
        //允许自动旋转为左横向      Home键在左
        //Screen.autorotateToLandscapeLeft = true;

        //允许自动旋转为右横向      Home键在右
        //Screen.autorotateToLandscapeRight = true;

        //允许自动旋转为纵向        Home键在下
        //Screen.autorotateToPortrait = true;

        //允许自动旋转为纵向倒着看  Home键在上
        //Screen.autorotateToPortraitUpsideDown = true;

        //指定屏幕显示方向
        //Screen.orientation = ScreenOrientation.Landscape;
        #endregion

        #endregion

        #region 静态方法
        //设置分辨率 一般移动设备不使用
        //参数三:是否全屏
        Screen.SetResolution(1920, 1080, false);
        #endregion
    }

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

14.Camera代码相关

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

public class lesson14 : MonoBehaviour
{
    public Transform obj;

    // Start is called before the first frame update
    void Start()
    {
        #region 重要静态成员
        //1.获取摄像机
        //如果用之前的知识 来获取摄像机
        //主摄像机的获取
        print(Camera.main.name);
        //获取摄像机的数量
        print(Camera.allCamerasCount);
        //得到所有摄像机
        Camera[] allCamrea = Camera.allCameras;
        print(allCamrea.Length);

        //2.渲染相关委托
        //摄像机剔除前处理的委托函数
        Camera.onPreCull += (c) =>
          {

          };
        //摄像机 渲染前 处理的委托
        Camera.onPreRender += (c) =>
          {

          };
        //摄像机 渲染后 处理的委托
        Camera.onPostRender += (c) =>
          {

          };
        #endregion

        #region 重要成员
        //1.界面上的参数 都可以在Camera中获取到
        //比如 下面这句代码 就是得到主摄像机对象 上的深度 进行设置
        Camera.main.depth = 10;

        //2.世界坐标转屏幕坐标
        //转换过后 x和y对应的就是屏幕坐标 z对应的 是 这个3D物体 离我们的摄像机有多远
        //我们会用这个来做的功能 最多的 就是头顶血条相关的功能
        Vector3 v=Camera.main.WorldToScreenPoint(this.transform.position);
        print(v);
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        //3.屏幕坐标转世界坐标
        //之所以改变z轴 是因为 如果不改 z默认为0
        //转换过去的世界坐标系的点 永远都是一个点 可以理解为 视口 相交的焦点
        //如果改变了z 那么转换过去的 世界坐标的点 就是相对于 摄像机前方多少单位的横截面上的世界坐标点
        Vector3 v = Input.mousePosition;
        v.z = 10;
        obj.position = Camera.main.ScreenToWorldPoint(v);
        //print(Camera.main.ScreenToWorldPoint(v));
    }
}

15.物理系统-碰撞检测

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

public class lesson16 : MonoBehaviour
{
    #region 知识点回顾
    //1.如何让两个游戏物体之间产生碰撞(至少一个刚体 和 两个碰撞器)
    //2.如何让两个物体之间碰撞时表现出不同效果(物理材质)
    //3.触发器的作用是什么(让两个物体碰撞没有物理效果,只进行碰撞处理)
    #endregion 

    #region 注意:碰撞和触发响应函数 属于 特殊的生命周期函数 也是通过反射调用

    #endregion

    #region 物理碰撞检测响应函数
    //碰撞触发接触时会 自动执行这个函数
    private void OnCollisionEnter(Collision collision)
    {
        //Collision类型的 参数 包含了 碰到自己的对象的相关信息

        //关键参数
        //1.碰撞到的对象碰撞器的信息
        //collision.collider

        //2.碰撞对象的依附对象(GameObject)
        //collision.gameObject

        //3.碰撞对象的依附对象的位置信息
        //collision.transform

        //4.触碰点数相关
        //collision.contactCount
        //接触点 具体的坐标
        //ContactPoint[] pos = collision.contacts;

        //只要得到了 碰撞到的对象的 任意一个信息 就可以得到它所有的信息

        print(this.name + "被" + collision.gameObject.name + "撞到了");
    }
    //碰撞结束分离时 会自动执行的函数
    private void OnCollisionExit(Collision collision)
    {
        print(this.name + "被" + collision.gameObject.name + "结束碰撞了");
    }
    //两个物体相互接触摩擦时 会不停的调用该函数
    private void OnCollisionStay(Collision collision)
    {
        print(this.name + "一直在和" + collision.gameObject.name + "接触");
    }
    #endregion

    #region 触发器检测响应函数
    //触发开始的函数 当第一次接触时 会自动调用
    private void OnTriggerEnter(Collider other)
    {
        print(this.name + "被" + other.gameObject.name + "触发了");
    }
    //触发结束的函数 当水乳相融的状态结束的时 会调用一次
    private void OnTriggerExit(Collider other)
    {
        print(this.name + "和" + other.gameObject.name + "结束水乳相融的状态了");
    }
    //当两个对象 水乳交融的时候 会不停调用
    private void OnTriggerStay(Collider other)
    {
        print(this.name + "正在和" + other.gameObject.name + "水乳相融");
    }
    #endregion

    #region 要明确什么时候会响应函数
    //1.只要挂载的对象 能和别的物体产生碰撞或者触发 那么对应的这6个函数 就能够被响应
    //2.6个函数不是说 我都得写 我们一般是根据需求来进行选择书写
    //3.如果是一个异形物体,刚体在父对象上,如果你想通过子对象上挂脚本检测碰撞是不行的 必须挂载到这个刚体父对象上才行
    //4.要明确 物理碰撞和触发器响应的区别
    #endregion

    #region 碰撞和触发器函数都可以写成虚函数 在子类去重写逻辑
    //一般会把想要重写的 碰撞和触发函数 写成保护类型的 没有必要写成 public 因为不会自己手动调用 
    //都是Unity 通过反射帮助我们自动调用的
    #endregion
}

16.物理系统-刚体加力

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

public class lesson17 : MonoBehaviour
{
    Rigidbody rigidBody;

    // Start is called before the first frame update
    void Start()
    {
        #region 刚体自带添加力的方法
        //给刚体加力的目标就是
        //让其有一个速度 朝向某一个方向移动

        //1.首先应该获取刚体组件
        rigidBody = this.GetComponent<Rigidbody>();

        //2.添加力
        //相对世界坐标
        //世界坐标系 z轴正方向加了一个力
        //rigidBody.AddForce(Vector3.forward*10);
        //如果想要在 世界坐标系方法中 让对象 相对于自己的面朝向动
        //rigidBody.AddForce(this.transform.forward * 10);

        //相对本地坐标
        //本地坐标系 z轴正方向加了一个力
        //rigidBody.AddRelativeForce(Vector3.forward * 10);


        //3.添加扭矩力,让其旋转
        //相对世界坐标
        //世界坐标系 y轴正方向加了一个力
        //rigidBody.AddTorque(Vector3.up * 10);

        //相对本地坐标
        //rigidBody.AddRelativeTorque(Vector3.up*10)

        //4.直接改变速度
        //这个速度方向 是相对于 世界坐标系
        //如果要直接通过改变速度 来让其移动 一定要注意这一点
        //rigidBody.velocity = Vector3.forward * 5;

        //5.模拟爆炸的效果
        //参数一:力的大小
        //参数二:爆炸点
        //参数三:力的范围
        //模拟爆炸的力 一定是 所有希望产生爆炸效果影响的对象 
        //都需要得到他们的刚体 来执行这个方法 才能都有效果
        //rigidBody.AddExplosionForce(100, Vector3.zero,10);
        #endregion

        #region 力的几种模式
        //第二个参数 力的模式 主要的作用 就是 计算方式不同而已
        //由于4种计算方式的不同 最终的移动速度就会不同
        //rigidBody.AddForce(Vector3.forward * 10, ForceMode.Acceleration);

        //动量定理
        //Ft = mv
        //v = Ft/m
        //F:力
        //t:时间
        //m:质量
        //v:速度

        //1.Acceleration
        //给物体增加一个持续的加速度,忽略其质量
        //v = Ft/m
        //F(0,0,10)
        //t:0.02s
        //m:默认为1
        //v = 10*0.02/1 =0.2m/s
        //每物理帧移动0.2m/s*0.02s=0.004m

        //2.Force
        //给物体添加一个持续的力,与物体的质量有关
        //v = Ft/m
        //F:(0,0,10)
        //t:0.02s
        //m:2kg
        //v = 10*0.02s/2kg = 0.1m/s
        //每物理帧移动 0.1m/s*0.02s = 0.002m

        //3.Impulse
        //给物体添加一个瞬间的力,与物体的质量有关,忽略时间 默认为1
        //v = Ft/m
        //F:(0,0,10)
        //t:默认为1
        //m:2kg
        //v = 10*1/2kg = 5m/s
        //每物理帧移动 5m/s*0.02s = 0.1m

        //4.VelocityChange
        //给物体添加一个瞬时速度,忽略时间、质量
        //v = Ft/m
        //F:(0,0,10)
        //t:默认为1
        //m:默认为1
        //v = 10*1/1kg = 10m/s
        //每物理帧移动 10m/s*0.02s = 0.2m
        #endregion

        #region 力场脚本
        //Constant Force
        //给物体一个持续的力
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        //如果你希望即使有阻力 也希望对象一直动 那你就一直推就行了
        //rigidBody = this.GetComponent<Rigidbody>();

        #region 刚体的休眠
        //获取刚体是否处于休眠状态 如果是休眠状态就唤醒它
        if (rigidBody.IsSleeping())
        {
            //唤醒
            rigidBody.WakeUp();
        }
        #endregion
    }
}

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值