Unity入门学习:Day05

(十)四元数

是一种超复数(x,y,z,w),轴角对。按YZX顺序一个轴一个轴的转

X=nx*sin(\frac{\Theta }{2})

Y=ny*sin(\frac{\Theta }{2})

Z=nz*sin(\frac{\Theta }{2})

W=cos(\frac{\Theta }{2})

例如,将一个物体绕Z轴旋转60度

cube.transform.rotation= new Quaternion(0,0,Mathf.Sin(30*Mathf.Deg2Rad),Mathf.Cos(30*Mathf.Deg2Rad))

Inspector面板中虽然是用Rotation这种欧拉角来显示的,但是底层旋转功能是用Quaternion来实现的。rotation显示的是Quaternion.Euler。

一、LookRotation:看向目标物体

//挂载在相机上
    public GameObject cube;
    public GameObject cylinder;
    // Start is called before the first frame update
    void Start()
    {
//结果是相机看向了cube
        Vector3 relativePos = cube.transform.position - transform.position;
        Quaternion rotation = Quaternion.LookRotation(relativePos);
        transform.rotation = rotation;
    }

二、Angel:它可以计算两个旋转之间的夹角。与Vector3.Angle()作用是一样的。返回欧拉角度

 //初始将cylinder绕y轴旋转90度,打印结果是90度。旋转-90度,也是打印90度。
 Debug.Log(Quaternion.Angle(cube.transform.rotation, cylinder.transform.rotation));

三、Euler:将一个欧拉形式的旋转转换成四元数形式的旋转。

//将cube的rotation值改为30,30,30
cube.transform.rotation = Quaternion.Euler(new Vector3(30,30,30));

  四、FromToRotation:得到从一个方向到另一个方向的旋转。

//摄像机做一个旋转,旋转的值是本身的前方到cube上方之间的旋转值。
//Quaternion.FromToRotation(Quaternion From,Quaternion To);
transform.rotation= Quaternion.FromToRotation(transform.forward, cube.transform.up);

 五、Slerp:基本意思就是球形插值地从一个角度旋转到另一个角度,其中,旋转匀速增加t。

//匀速看向cube目标物体
 transform.rotation = Quaternion.Slerp(transform.rotation, cube.transform.rotation, 0.01f);

六、四元数相乘

两个四元数相乘,得到的新的四元数,就是两个旋转角度的叠加。

(十一)对象池

 取子物体或者生成新子物体函数

    /// 
    /// 从Pool里面拿出子对象或者生成新对象
    /// 
    public void GetObjFromPoor()
    {        
        //如果缓存池不空,那么就从中拿出物体
        if (Pool.childCount > 0)
        {
            //利用循环按照所需要的数量将池子中的物体拿出来,并且设置为可见,父节点设置为bulletFather
            //利用协程等待一定时间后,将子弹放回池子。
            for (int i = 0; i < numsWeNeed; i++)
            {
                GameObject obj = FindChild("bullet", Pool).gameObject; 
                obj.SetActive(true);
                obj.transform.parent = bulletFather;
                StartCoroutine(Delayed(obj));//开启协程
            } 
        }
       //如果缓存池空了,生成新的物体
        else
        {
            InstiateByCircle();
        }
    }

将物体放回对象池

    /// 
    /// 将物体放回池子,设为false,状态置0.
    /// 
    /// 
    public void PushObjToPoor(GameObject obj)
    {
        obj.SetActive(false);
        放回池子的东西放在一个父节点下,便于管理
        obj.transform.position = Vector3.zero;
        obj.transform.parent = Pool.transform;
    }

 协程函数

    /// 
    /// 协程函数,将物体放回池子
    /// 
    /// 
    /// 
    IEnumerator Delayed(GameObject obj)
    {
        yield return new WaitForSeconds(delayTime);//3秒之后执行之后的语句
        PushObjToPoor(obj);
    }

案例示例:环形发射子弹

Unity入门学习:Day05_第1张图片

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

public class Fire1 : MonoBehaviour
{
    public GameObject bt;//待生成的子弹预制体,通过resource加载
    public Transform bulletFather;//子弹挂载的父节点
    public Transform Pool;//对象池

    public float delayTime = 2;//协程函数等待时间

    public int numsWeNeed=12;//需要取出来的数量
    void Start()
    {
        bt= Resources.Load("bullet");
    }

    void Update()
    {
        //生成环形子弹,放到PoorActiveTrue根节点下
        if (Input.GetMouseButtonDown(0))
            GetObjFromPoor();    
        transform.rotation *= Quaternion.Euler(new Vector3(0,1,0));
    }

    /// 
    /// 生成环形子弹
    /// 
    /// 环形子弹的列表
    public void InstiateByCircle()
    {
        for (int i = 0; i < 12; i++)
        {
            GameObject obj = Instantiate(bt, transform.position, Quaternion.Euler(0, i * 30, 0)*transform.rotation);
            //生成的子弹都加入到bullets列表中
            //bullets.Add(obj);
            obj.transform.parent = bulletFather;
            obj.name = "bullet";
            StartCoroutine(Delayed(obj));//开启协程
        }
    }
    /// 
    /// 协程函数,将物体放回池子
    /// 
    /// 
    /// 
    IEnumerator Delayed(GameObject obj)
    {
        yield return new WaitForSeconds(delayTime);//3秒之后执行之后的语句
        PushObjToPoor(obj);
    }
  
    /// 
    /// 从Pool里面拿出子对象或者生成新对象
    /// 
    public void GetObjFromPoor()
    {        
        //如果缓存池不空,那么就从中拿出物体
        if (Pool.childCount > 0)
        {
            //利用循环按照所需要的数量12将池子中的物体拿出来,并且设置为可见,父节点设置为bulletFather
            //利用协程等待一定时间后,将子弹放回池子。
            for (int i = 0; i < numsWeNeed; i++)
            {
                GameObject obj = FindChild("bullet", Pool).gameObject; 
                obj.SetActive(true);
                obj.transform.parent = bulletFather;
                StartCoroutine(Delayed(obj));//开启协程
            } 
        }
       //如果缓存池空了,生成新的物体
        else
        {
            InstiateByCircle();
        }
    }
    /// 
    /// 将物体放回池子,设为false,状态置0.
    /// 
    /// 
    public void PushObjToPoor(GameObject obj)
    {
        obj.SetActive(false);
        放回池子的东西放在一个父节点下,便于管理
        obj.transform.position = Vector3.zero;
        obj.transform.parent = Pool.transform;
    }

    /// 
    /// 通过深度递归查找根节点下的某子物体Transform
    /// 
    /// 待查找的物体名称
    /// 挂载脚本的物体,查找的起点
    /// 查找的结果
    Transform FindChild(string objectName, Transform Start)//2  4
    {
        if (objectName == Start.name) //起点位置即为要找的物体,找到就退出,这是退出条件
            return Start;
        if (Start.childCount < 1) //起点位置不能没有子物体,不然就退出,这是退出条件
            return null;
        #region   这才是递归的核心,深度递归    
        Transform obj = null;
        for (int i = 0; i < Start.childCount; i++)
        {
            Transform t = Start.GetChild(i);
            obj = FindChild(objectName, t);
            if (obj != null)
            {
                break;
            }
        }
        return obj;
        #endregion
    }
}

(十二)射线(Ray)

用射线去检测物体,返回检测信息

Unity默认情况下提供了 3 种射线检测器:

  • Graphic Raycaster - 用于 UI 元素
  • Physics 2D Raycaster - 用于 2D 物理元素
  • 物理射线投射器 (Physics Raycaster) - 用于 3D 物理元素

由于学习2D为主,则主要关注Physics2D Raycaster。

Physics2D Raycaster

void Update()
    {
        //最基础的射线,被检测物体一定要有碰撞体
        RaycastHit2D info = Physics2D.Raycast(transform.position, Vector2.right);
        Debug.Log("被检测到物体的transform:"+info.transform);
        Debug.Log("被检测到物体的质心位置信息:" + info.centroid);
        Debug.Log("被检测到物体的碰撞体:" + info.collider);       
        Debug.Log("从射线原点到撞击点的距离:" + info.distance);
        Debug.Log("射线上发生命中的距离的分数:" + info.fraction);
        Debug.Log("射线命中的表面的法线矢量:" + info.normal);
        Debug.Log("世界空间中射线命中碰撞体表面的点:" + info.point);
        Debug.Log("附加到命中的对象的 Rigidbody2D:" + info.rigidbody);

        //假如我要检测在Layer【14: "Enemy"】发生的碰撞
        //也就是说:
        //int LayerID = 14
        //string LayerName = "Enemy"

        //使用Layer ID 检测在Layer14层发生的碰撞
        info = Physics2D.Raycast(transform.position, Vector2.right, default, 1 << 14);

        //使用Layer ID 检测在Layer12、14层发生的碰撞   (1 << 12) | (1 << 14) 表示包含12、14层 (不包含其余层)
        info = Physics2D.Raycast(transform.position, Vector2.right, default, (1 << 12) | (1 << 14));

        //使用Layer ID 检测不在Layer14层发生的碰撞 ~(1<<14) 表示不包含14层(包含其他所有层)

        info = Physics2D.Raycast(transform.position, Vector2.right, default, ~(1 << 14));
    }

(十三)异步加载和协程

异步加载不会引起代码阻塞;

协程:协程不是多线程,是假的“多线程”;

           应用场景:1.WWW加载网络数据;2.异步加载资源、场景;

yield关键字是最重要的,直译是屈服、让步于,就是等待其他内容的执行,其他内容执行完了再回到这个协程执行剩下的语句。

下面看八道练习题帮助理解协程执行逻辑。

(1条消息) 八道练习题教你轻松学会运用Unity中的协程用法_听雨眠丨的博客-CSDN博客

1.开始协程:我们通过StartCoroutine()函数来调用协程函数。

        调用协程的方法有两种,分别是StartCoroutine(/这里直接调用方法,添加参数/),另一种是StartCoroutine(/这里填写”字符串的方法名字”,方法参数/)。

        第一种方法的优势在于可以调用多个参数的方法,后一种方法只能调用不含参数或只包含一个参数的协程方法。但是第一种方法不能通过StopCoroutine(/这里填写”字符串的方法名”/)来结束协程,只能通过StopAllCoroutines来结束。后一种则可以通过StopCoroutine来结束对正在执行的协程的调用。
 

2.停止协程

        在一个协程开始后,同样会对应一个结束协程的方法StopCoroutine与StopAllCoroutines两种方式,但是需要注意的是,两者的使用需要遵循一定的规则,在介绍规则之前,同样介绍一下关于StopCoroutine重载:

        StopCoroutine(string methodName):通过方法名(字符串)来进行
        StopCoroutine(IEnumerator routine):通过方法形式来调用
        StopCoroutine(Coroutine routine):通过指定的协程来关闭
        刚刚我们说到他们的使用是有一定的规则的,那么规则是什么呢,答案是前两种结束协程方法的使用上,如果我们是使用StartCoroutine(string methodName)来开启一个协程的,那么结束协程就只能使用StopCoroutine(string methodName)和StopCoroutine(Coroutine routine)来结束协程。

3.暂停协程:使用yield关键字

yield return null; // 下一帧再执行后续代码
yield return 0; //下一帧再执行后续代码
yield return 6;//(任意数字) 下一帧再执行后续代码
yield break; //直接结束该协程的后续操作
yield return asyncOperation;//等异步操作结束后再执行后续代码
yield return StartCoroution(/*某个协程*/);//等待某个协程执行完毕后再执行后续代码
yield return WWW();//等待WWW操作完成后再执行后续代码
yield return new WaitForEndOfFrame();//等待帧结束,等待直到所有的摄像机和GUI被渲染完成后,在该帧显示在屏幕之前执行
yield return new WaitForSeconds(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间会受到Time.timeScale的影响);
yield return new WaitForSecondsRealtime(0.3f);//等待0.3秒,一段指定的时间延迟之后继续执行,在所有的Update函数完成调用的那一帧之后(这里的时间不受到Time.timeScale的影响);
yield return WaitForFixedUpdate();//等待下一次FixedUpdate开始时再执行后续代码
yield return new WaitUntil()//将协同执行直到 当输入的参数(或者委托)为true的时候....如:yield return new WaitUntil(() => frame >= 10);
yield return new WaitWhile()//将协同执行直到 当输入的参数(或者委托)为false的时候.... 如:yield return new WaitWhile(() => frame < 10);

使用UniTask代替协程

 1.UniTask有哪些优点

 2.插件安装步骤

方法一:通过Git url连接安装(推荐)

在PackageManage里面通过链接来安装,注意如果电脑未安装git会报错。

https://github.com/Cysharp/UniTask.git?path=src/UniTask/Assets/Plugins/UniTask

方法二:下载压缩包插件安装

3.基础操作

参考:UniTask · 开发文档集合

(十四)特殊文件夹

参考自: Unity特殊文件夹以及各平台Application.xxxPath的路径图文详解教程

1.  StreamingAssets ―― 二进制流 资源文件夹

默认情况下, Unity 是没有 StreamingAssets 文件夹的,我们需要自行创建

StreamingAssets 与 Resources 区别:

Resources文件夹下的资源打包发布时,会进行一次压缩和加密

StreamingAssets 文件夹下的资源打包发布时,原封不动(不进行任何处理)

所以 StreamingAssets 一般放置数据资源文件,比如 Json /Xml 等数据文件

在PC/MAC中可实现对文件的“增删改查”等操作,但在移动端是一个只读路径。

返回的路径 Application.streamingAssetsPath
Mac OS / Windows Application.dataPath + "/StreamingAssets"
iOS Application.dataPath + "/Raw"
Android "jar:file://" + Application.dataPath + "!/assets/"


 

 

在 Android 平台上使用压缩.apk文件,streamingAsset 中的数据文件包含所以需要使用 UnityWebRequest 类访问资产,才可对数据进行正确读取

using System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;


/// 
/// 测试获取 StreamAssets 下的数据资源,(图片),并显示在UI上
/// 
public class ChinarWebRequest : MonoBehaviour
{
    void Start()
    {
        StartCoroutine(SendUrl(Application.streamingAssetsPath + "/Chinar.png"));
    }


    /// 
    /// 发起请求
    /// 
    /// 地址
    private IEnumerator SendUrl(string url)
    {
        using (UnityWebRequest uw = UnityWebRequest.Get(url))
        {
            yield return uw.SendWebRequest();
            if (uw.error != null)
            {
                Debug.Log(uw.error);
            }
            else
            {
                if (uw.responseCode == 200) //200表示接受成功
                {
                    Texture2D texture = new Texture2D(400, 350);                                                            //创建Texture
                    texture.LoadImage(uw.downloadHandler.data);                                                             //加载Image
                    Sprite sprite = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.one / 2); //得到精灵对象
                    GameObject.Find("Chinar-Image").GetComponent().sprite = sprite;                                  //赋值到UI上
                }
            }
        }
    }
}

2.Persistent ―― 持久数据 文件夹 

常用于存储一些可持久化的用户数据文件

此目录只对应一个应用程序,只要应用不被卸载 / 或是公司名且应用名称不做更改 / 此目录中的文件不会进行任何更改!

只能在应用程序运行时,才能读写操作,建议将获得的文件,或是用户的基本信息,等等数据文件,保存到此目录中。例如,将 AssetBundle 包中读取的数据,写入该目录中。

3.总结

Application.dataPath 此属性用于返回程序的数据文件所在文件夹的路径
Application.streamingAssetsPath
此属性用于返回流数据的缓存目录,返回路径为相对路径,适合设置一些外部数据文件的路径。
Application.persistentDataPath 此属性用于返回一个持久化数据存储目录的路径,可以在此路径下存储一些持久化的数据文件
pplication.temporaryCachePath
此属性用于返回一个临时数据的缓存目录

各路径在几种平台下的目录:

Android Windows
Application.dataPath /data/app/xxx.xxx.xxx.apk /Assets
Application.streamingAssetsPath jar:file:///data/app/xxx.xxx.xxx.apk/!/assets /Assets/StreamingAssets
Application.persistentDataPath /data/data/xxx.xxx.xxx/files C:/Users/xxxx/AppData/LocalLow/CompanyName/ProductName
Application.temporaryCachePath /data/data/xxx.xxx.xxx/cache C:/Users/xxxx/AppData/Local/Temp/CompanyName/ProductName

你可能感兴趣的:(Unity基础,unity)