zoukankan      html  css  js  c++  java
  • Siki_Unity_2-1_API常用方法和类详细讲解(上)

    Unity 2-1 API常用方法和类详细讲解(上)

    任务1&2:课程前言、学习方法 && 开发环境、查API文档

    API: Application Programming Interface 应用程序编程接口

    查看Unity文档和API手册:

    help->Unity Manual/ Scripting Reference

    任务3&4:事件函数 && 事件函数的执行时机

    每一个脚本默认都是继承MonoBehaviour的
      MonoBehaviour是继承Behaviour的
      Behaviour是继承Component的
      Component是继承Object的
      因此Script脚本是一个Object

    Manual->Scripting->Scripting Overview->Execution Order of Event Functions

    Reset() 只会在Editor模式下触发,运行时或build完以后就不会触发了
      当在Editor模式下(运行的时候不触发),当script第一次attached到GameObject时/
      当GameObject的Reset按钮被按下时会触发。
      用于initialise the script's properties

    Awake() 当场景开始运行时 当一个Prefab被实例化出来的时候会触发
      Awake总是在任何Start()之前被触发
      If a GameObject is inactive during start up, Awake is not called until it's made active

    OnEnable() 当场景开始运行时 当一个Object被enabled时会触发
      前提是这个Object为active时
    OnDisable() 当一个Object becomes disabledinactive时

    OnLevelWasLoaded() is to inform the game that a new level has been loaded

    Start() is called before the first frame update (only if the script instance is enabled)

    FixedUpdate(), Update(), LateUpdate()

    FixedUpdate() 会先调用,之后是Update(),最后是LateUpdate()
      FixedUpdate是每秒固定调用N次
      Update和LateUpdate是每帧调用一次 -- 与运行环境有关

    FixedUpdate() all physics calculations and updates occur immediately after FixedUpdate. When applying
      movement calculations inside FixedUpdate, you don't need to multiply your values by Time.deltaTime
      since FixedUpdate is called on a reliable timer, which is independent of the frame rate.
      一般把与物理有关的更新写在FixedUpdate里 -- 可以保证物体的运动是平滑的,

    Update() is called once per frame.

    LateUpdate() is called once per frame, after Update has finished. A common use for LateUpdate would
      be a following third-person camera: make the character move and turn inside Update, you can perform
      all camera movement and rotation calculations in LateUpdate -- to ensure that the character has moved 
      completely before the camera tracks its position.

    OnTrigger...()

    OnCollision...()

    yield WaitForFixedUpdate() 可以在FixedUpdate()中调用yield WaitForFixedUpdate()

    OnMouse...() 是Input events

    Scene Rendering之后详叙

    OnApplicationPause() is called after the frame where the pause occurs but issues another frame before
      actually pausing. One extra frame will be issued after OnApplicationPause is called to allow the game to
      show graphics that indicate the paused state. 点击Unity中间暂停按钮的时候会调用。

    OnApplicationQuit() 退出游戏的时候触发。
      It is called before the application is quit; In the editor, it is called when the user stops playmode.

    OnDisable() is called when the behaviour becomes disabled or inactive.

    OnDestroy() is called after all frame updates for the last frame of the object's existence

    Update和LateUpdate的数量是一样的,而与FixedUpdate的数量不同
    Start在第一个xxxUpdate之前
    OnApplicationPause在这里出现了,点击Unity中的暂停键也没反应,不知为何
    uncheck gameoject的勾选框,执行了OnDisable(),重新勾选,执行了OnEnable()
    禁用的时候所有的xxxUpdate也不会进行更新了

    运行结束后:OnApplicationQuit-->OnDisable->OnDestroy

    任务5:Time类中的静态变量 

    Time类

    Time.captureFramerate -- 设置帧的速率,进而进行屏幕截图

    Time.deltaTime -- 当前帧所用时间(单位秒)
      经常在Update中使用:* Time.deltaTime 表示一秒

    Time.fixedDeltaTime -- 和FixedUpdate()与Update()的区别相似

    Time.smoothDeltaTime -- 平滑,不会变化过大的deltaTime

    Time.time -- 从游戏开始(start of the game)到当前帧开始(the time at the beginning of this frame)所用的时间

    Time.fixedTime -- 从游戏开始到最新一个FixedUpdate()开始(the latest FixedUpdate has started)所用的时间

    Time.unscaledTime

    Time.realtimeSinceStartup -- the real time in seconds since the game started, not affected by Time.timeScale.
      It keeps increasing while the player is paused. Using realtimeSinceStartup is useful when you wanna pause the
      game by setting Time.timeScale=0, but still want to be able to measure time somehow.

    Time.timeSinceLevelLoad -- 从the last level has been loaded到this frame has started所用的时间

    Time.frameCount -- the total number of frames that have passed

    Time.timeScale -- the scale at which the time is passing, 可以用于调整游戏的播放速率,默认为1

    注:

    Time.time≈Time.fixedTime 游戏运行时间
    Time.unscaledTime, Time.realtimeSinceStartup 不受timeScale影响 (不受游戏暂停影响), 但是time会被影响
    Time.time, Time.unscaledTime 在FixedUpdate()中调用时,会返回对应的fixed的值fixedTimefixedUnscaledTime

    time = timeSinceLevelLoad : start~~the beginning of this frame
    fixedTime略大于time : FixedUpdate在Update之后
    fixedDeltaTime = 0.02 -- 每秒50帧,Edit->Project Setting->Time->Time Manager中可以设置
    由于fixedDeltaTime设置为0.02,则deltaTime会在0.02上下波动
    smoothDeltaTime的波动比deltaTime的平滑很多
    timeScale = 1(默认值)

    任务6:Time.deltaTime和Time.realtimeSinceStartup的使用

    Time.deltaTime的使用 -- 常用于控制物体的移动

    控制一个物体的移动
    public Transform cube;
    cube.Translate(Vector3.forward);

    -- cube物体会移动很快:调用一次移动一米,每秒调用很多次

    -> cube.Translate(Vector3.forward * 3 * Time.deltaTime);  // 速度*当前帧所用时间=当前帧运行距离:3m/s

    Time.timeScale = 1f;  // 设置timeScale
    --> Time.deltaTime会乘以timeScale
    timeScale=0.5f; 进行慢动作回放/ timeScale=2f; 两倍播放速度等等
    若timeScale=0; cube就不会移动了,常用timeScale=0来暂停游戏,前提是所有物体的控制都用了deltaTime

    Time.realtimeSinceStartup的使用 -- 常用于性能测试

    比如要测试某个方法、某种处理方式是否耗费性能

    例:测试Method1()和Method2()哪一个更耗费性能

    Method1() {
        int i = 1 + 2;
    }
    Methods2() {
        int i = 1 * 2;
    }
        
        float time1 = Time.realtimeSinceStartup;
        int runCount = 1000000;
        for(int i=0; i<runCount; i++) {
            Method1();
        }
        float times2 = Time.realtimeSinceStartup;
        Debug.Log(time2 - time1);
        // 把Method2换成Method1再执行一次,比较两次的输出即可

    一般还有一个对照组:Method0() {}  方法体为空

    任务7&8:创建游戏物体的三种方法 && 并添加组件

    GameObject类

    与实例化有关的方法:

    Instantiate()

    CreatePrimitive()

    1. 第一种创建物体的方法 new GameObject();

    // 创建游戏物体
    // 创建一个名为New Game Object的Empty Object, with Component Transform
    new GameObject();
    // 名为Cube
    GameObject cube = new GameObject("Cube");
    cube.AddComponent<Rigidbody>();

     

    这个方法一般用来创建空物体,用于放置其他物体

    2. 第二种创建物体的方法 GameObject.Instantiate();

    通常为初始化一个Prefab或复制另外一个游戏物体

    使用Instantiate()创建的物体的名字默认跟上(Clone)

    // 创建游戏物体 GameObject.Instantiate()
    // 根据Prefab
    // 根据另外一个游戏物体
    // prefab在Unity中可以被prefab或gameObject赋值
    GameObject.Instantiate(prefab); 

    3. 第三种创建物体的方法 GameObject.CreatePrimitive();

    通常用于创建一些原始的形状,如Create->3D Object->Cube/Sphere/Capsule/Cylinder等等

    GameObject.CreatePrimitive(PrimitiveType.Plane);
    GameObject sphere =  GameObject.CreatePrimitive(PrimitiveType.Sphere);

    任务9:启用和禁用游戏物体

    tag -- 属性,多用于判断游戏物体的分类

    transform -- 所有GameObject都有一个transform组件,且不能被删除

    activeSelf -- 是否locally处于激活状态
    activeInHierarchy -- Is the GameObejct active in the scene? -- 多用于判断该物体是否激活
      二者区别:B是A的子物体,禁用A,则A.activeInHierarchy=false; B.activeInHierarchy=false
      但是A.activeSelf=false; B.activeSelf=true; (在Inspector中B是checked状态)

    gameObject.SetActive(true/false); -- 设置gameObject的activeInHierarchy状态

    禁用了游戏物体后,它还是在内存中存储的,因此可以获取对应的属性
    只不过物体不会在Scene中显示,Update()也不再执行,不需要渲染,不耗费性能了

    任务10&11&12&13&14:GameObject、Component、和Object的关系
    && UnityEngine下Object && GameObject拥有的静态方法
    && 游戏物体间消息的发送和接收
    && 得到组件的方法

    一个游戏(Game)由多个场景(Scene)组成
    一个场景(Scene)由多个游戏物体(GameObject)组成
    一个游戏物体(GameObject)由多个组件(Component)组成

    C#的共同基类:System.Object

    Unity中类的共同基类:UnityEngine.Object

    Component和GameObject都是继承自UnityEngine.Object的,他们有许多相同的属性

    Object中的属性:
    name -- 通过GameObject或通过它的Component获取到的name都是GameObject的name

    Object中的方法:
    Destroy() -- Removes a gameobject/ component/ asset.
      调用后,游戏物体在场景中会立刻被删除,但是还没被回收;在Unity中会把将要销毁的游戏物体放入垃圾池统一管理
      Destroy(gameObject);
      Destroy(this); // script
      Destroy(rigidbody); // remove component
      Destroy(gameObject, 5);  // removes gameObject in 5 seconds

    DestroyImmediate() -- Destroy the object immediately, strongly recommended to use Destroy instead.
      该方法会立刻删除游戏物体,有可能导致空指针

    DontDestroyOnLoad(gameObject) -- A场景跳转到B场景时,可以调用DontDestroyOnLoad()
      一般而言跳转后,A场景中的所有游戏物体都会被销毁,调用后gameObject不会被销毁
      一般用于设置一个两个场景共享的游戏物体

    FindObject(s)OfType -- 根据组件类型进行全局查找,找到符合type类型的组件(返回找到的第一个)
      注意,只会查找激活了的游戏物体
      Light light = FindObjectOfType<Light>();
      light.enabled = false;
        -- 对于一个组件,使用.enabled来控制激活,对于一个对象,使用SetActive()来控制。
      Transform[] transforms = FindObjectsOfType<Transform>();
      foreach(Transform fransform in transforms) { ... }

    Instantiate()
      Instantiate(Object original);
      Instantiate(Object original, Transform parent);  // 设置为parent的子对象
      Instantiate(Object original, Transform parent, bool instantiateInWorldSpace);  // 是否用worldSpace -- 位置为局部或是世界
      Instantiate(Object original, Vector3 position, Quaternion rotation);
      Instantiate(Object original, Vector3 position, Quaternion rotation, Transform parent);

    GameObject的变量:

    activeInHierarchy; activeSelf; isStatic; layer; scene; tag; transform

    GameObject的静态方法:

    CreatePrimitive

    Find -- 对全局进行遍历查找;耗费性能,尽量不要在 Update中调用,少在Start中调用
      GameObject gameObject = GameObject.Find("Main Camera");
      gameObject.SetActive(false);

    FindGameObjectWithTag -- 返回游戏物体的数组;在标签范围内进行查找 -- 较快
      GameObject[] gameObjects = GameObject.FindGameObjectsWithTag("Main Camera");

    FindWithTag -- 返回查找到的第一个符合条件的游戏物体

    注意:一般而言,需要进行null的判断,因为有可能出现tag错误或无激活物体等情况

    GameObject的方法:通过对象进行调用

    AddComponent

    GetComponent -- 如果游戏物体上有多个相关组件,则会返回得到的第一个 
    GetComponents

    GetComponentInChildren -- 该游戏物体以及所有子物体
    GetComponentsInChildren

    GetComponentInParent -- 该游戏物体以及所有直系祖先
    GetComponentsInParent

    BroadcastMessage(string methodName, object paramter=null, SendMessageOptions opts) -- 广播消息
      calls the method named methodName on every script in this game object or any of its children
      搜索自身和子物体,若有methodName()则调用
      优点:减少游戏物体之间的耦合性,因为若是使用SendMsg则需要先得到游戏物体的指向
      SendMessageOptions.DontRequireReceiver:即使没有Receiver也不会报错
      SendMessageOptions.RequireReceiver: 若是没有Receiver,会报错

    target.BroadcastMessage("Attack", null, SendMessageOptions.DontRequireReceiver);
    target.BroadcastMessage("Attack", null, SendMessageOptions.RequireReceiver);

    SendMessage(string methodName, object value=null, SendMessageOptions opts)
      -- 针对某个对象发送消息,但不会对其子对象发送;对该对象中所有methodName()进行调用
      public GameObject target;
      target.SendMessage("Attack");

    SendMessageUpwards(...) -- 针对该对象以及其所有ancestors(父亲&爷爷等),与BroadcastMessage相反
      注意:父亲只会有一个,其他的是叔叔
      target.SendMessageUpwards("Attack", null);

    CompareTag -- 判断两个物体的标签是否相同

    SetActive -- 激活/禁用游戏物体

    注意:
      对于BroadcastMessage, SendMessage, SendMessageUpwards, CompareTag,
      GetComponent(s), GetComponent(s)InChildren, GetComponent(s)InParent,
      在对组件Component进行这些操作时,就是在对组件所在物体进行相应操作

    Component组件:

    Transform
    Rigidbody
    Mesh Renderer
    MeshFilter
    Collider
    NavemeshAgent
    Animation
    Animator
    自定义脚本 .cs

    任务15&16:MonoBehaviour简介
    && 17:Invoke的使用
    && 18&19&20:协程的执行
    && 21:鼠标相关事件函数

    MonoBehaviour中大多数都是Message事件,由Unity自动调用

    变量:

    runInEditMode:By default, script components are only executed in play mode. By setting this property, the MonoBehaviour will have   its callback functions executed while the Editor is not in playmode.
      控制脚本在EditMode运行
    对应:[ExecuteInEditMode]

    useGUILayout:略

    继承的变量:

    enabled:禁用/ 激活该脚本
      注:禁用脚本是指禁用了脚本的Update方法,因此如果某脚本没有写Update(),则也不会出现勾选的框

    isActiveAndEnabled:该脚本是否被激活(activeInHierarchy用来判断物体时候被激活)

    gameObject:该脚本所在的游戏物体

    tag, transform, name -- 脚本所在的游戏物体的对应属性

    hideFlags:

    静态方法:

    print() -- 是MonoBehaviour的静态方法,因此只能在MonoBehaviour中调用
    而Debug.Log()在任何位置都能调用

    方法:

    任务17:Invoke的使用

    Invoke:

    CancelInvoke() -- 取消当前MonoBehaviour中(不是当前对象中)所有的Invoke calls

    Invoke("methodName", time) -- Invokes the mothod methodName in time seconds

    InvokeRepeating("name", time, rate) -- invoke methodName(), then repeatedly every repeatRate seconds
      多用于与CancelInvoke()配合使用

    IsInvoking -- 判断是否有Invoke方法pending(正在被调用) -- Invoke后到执行前均返回true
      相当于,Invoke会将某方法加入一个队列,IsInvoking会判断该方法是否在该队列中
      i.e. 在2.0f秒之内再次按下Space时,不会执行Invoke("LaunchProjectile")

    void Update() {
        if(Input.GetKeyDown(KeyCode.Space) && !IsInvoking("LaunchProjectile")) {
            Invoke("LaunchProjectile", 2.0f);
        }
    }
    void LaunchProjectile() {
        Rigidbody instance = Instantiate(projectile);
        instance.velocity = Random.insideUnitSphere * 5;
    }

    任务18&19&20:协程的执行

    协程:Coroutine

    程序的正常执行顺序:顺序执行,遇到方法则进入方法后顺序执行方法

    协程:如果方法是一个协程方法,则可看作是一个新的Thread,可能同步执行,可能有先后,具体看CPU如何调度
    协程方法好处:不会阻塞当前方法的运行;可以进行协程方法自身的暂停

    StartCoroutine(); StopAllCoroutines(); StopCoroutine()

    StartCoroutine(IEnumerator routine)/ StartCoroutine(string methodName, object value=null)
      -- Starts a coroutine
      注:两种写法与StopCoroutine()的两种写法分别对应,不能混淆
      注:通过methodName启动协程时,最多只能传递一个参数

    void Start () {
        print("before changing");
        // 开始Coroutine
        StartCoroutine(ChangeColor());
        print("after changing");
    }
    // Coroutine
    // 1. 返回值为IEnumerator
    // 2. 返回参数的时候使用yield return
    // 3. 使用StartCoroutine(method())进行调用
    IEnumerator ChangeColor () {
        // 等待三秒
        yield return new WaitForSeconds(3f);
        print("Color: before changing");
        cube.GetComponent<MeshRenderer>().material.color = Color.blue;
        print("Color: after changing");
        // 返回null
        yield return null;
    }

    例子:通过协程实现颜色渐变

    public class API8CoroutineFadeColor : MonoBehaviour {
    
        public GameObject cube;
        // private bool started = false;
        
        // Update is called once per frame
        void Update () {
            // if (Input.GetKeyDown(KeyCode.Space) && !started) {
            if(Input.GetKeyDown(KeyCode.Space)) {
                StartCoroutine(Fade());
            }
        }
    
        IEnumerator Fade() {
            // started = true;
            for(float i = 1; i>=0; i -= 0.1f) {
                cube.GetComponent<MeshRenderer>().material.color=new Color(i,i,i);
                yield return new WaitForSeconds(0.1f);
            }
            yield return null;
        }
    }

    方法2:利用差值 Lerp()

    IEnumerator Fade() {
        while(true) {
            Color startColor = cube.GetComponent<MeshRenderer>().material.color;
            Color newColor = Color.Lerp(startColor, Color.red, 0.02f);
            cube.GetComponent<MeshRenderer>().material.color = newColor;
            yield return new WaitForSeconds(0.02f);
            Debug.Log(Mathf.Abs(newColor.g - Color.red.g));
            if(Mathf.Abs(newColor.g - Color.red.g) <= 0.1f) {
                break;
            }
            yield return null;
        }
    }

    StopAllCoroutines() -- Stops all coroutines running on this behaviour

    StopCoroutine(string methodName)/ StopCoroutine(IEnumerator routine)
      -- Stops the first coroutine named methodName, or the coroutine stored in routine running on this behaviour
      使用方法与StartCoroutine对应

    例1:通过IEnumerator实现

    private IEnumerator coroutine;
    
    void Update () {
        // if (Input.GetKeyDown(KeyCode.Space) && !started) {
        if(Input.GetKeyDown(KeyCode.Space)) {
            coroutine = Fade();
            StartCoroutine(coroutine);
        }
        if (Input.GetKeyDown(KeyCode.S)) {
            StopCoroutine(coroutine);
        }
    }

    例2:通过直接填写方法名methodName实现 (最多只能传递一个参数)

    void Update () {
        if(Input.GetKeyDown(KeyCode.Space)) {
            StartCoroutine("Fade");
        }
        if (Input.GetKeyDown(KeyCode.S)) {
            StopCoroutine("Fade");
        }
    }

    任务21:鼠标相关事件函数

    OnMouseXXX():对象为GUIElement or Collider
      注:一般物体需要添加Collider后才有效

    OnMouseDown() -- 鼠标按下的瞬间调用
    OnMouseDrag() -- 鼠标按下后进行拖拽的时候
    OnMouseEnter() -- 鼠标移入的时候
    OnMouseExit() -- 鼠标移出的时候
    OnMouseOver() -- 鼠标停留在物体上方的时候
    OnMouseUp() -- 鼠标松开的时候(与OnMouseDown相对)
    OnMouseUpAsButton() -- 鼠标在之前按下的物体上方松开时 (与OnMouseDown相对)

    任务22&23&24:Mathf详解
    &25:Lerp 差值运算
    &26:MoveTowards 匀速运动
    &27:PingPong 往复运动

    静态变量:Read-Only

    Deg2Rad/ Rad2Deg -- Degree <-> Radian: Deg2Rad=2*PI/360; Rad2Deg=360/(2*PI)

    Epsilon -- A tiny positive floating point value:
      anyValue +/- Epsilon = anyValue;
      0 +/- Epsilon = Epsilon

    Infinity/ NegativeInfinity

    PI

    静态方法:

    Abs() -- absolute value

    Approximately() -- Compares two floats and returns true if they're similar (within Eposilon)
      Floating point imprecision makes comparing floats using "=" inaccurate.

    Cell() -- 向上取整(进一法),注:负数取大的(Mathf.Cell(-10.7f)=-10)
    CeilToInt() -- 返回值为Int类型

    Clamp(value, min, max) -- 夹紧
      return min if value<min; return max if value>max; else return value
      常用于TakeDamage()或GainHealth() -- 受伤或补血不用判断hp超出
        hp = Mathf.Clamp(hp, 0, 100);
    Clamp01(value) -- 夹紧在0,1之间,等同于Clamp(value, 0, 1);

    ClosestPowerOfTwo(int value) -- returns the nearest power of two value
      ClosestPowerOfTwo(30)=32 -- 2^5=32

    Exp(float power) -- e to the power of "power"

    Pow(float f, float p) -- f to the power of p

    Sqrt(f) -- square root of f

    DeltaAngle(float current, float target) -- the shortest difference btw two given angles given in degrees 最小夹角

    Floor() -- 向下取整(退一法),注:负数取小的(Mathf.Cell(-10.7f)=-11)
    FloorToInt()

    Max(a,b,c...)/ Max(float[] values)/ Min(...)

    MoveTowards() -- Moves a value current towards target

    Lerp(float a, float b, float t) --  插值运算
      t为0~1的一个比例,t<=0时返回a,t>=1时返回b

    Start() {
        cube.position = new Vector3(0, 0, 0);
    }
    
    Update() {
        float x = cube.position.x;
        float newX = Mathf.Lerp(x, 10, 0.1f); 
       // 从x~10 -- 从当前位置向目标位置移动10%
       // 不是匀速运动,移动越来越慢,而且只会无限接近目标位置
    cube.position = new Vector3(newX, 0, 0); }

    一般情况会使用Mathf.Lerp(x, 10, Time.deltaTime * speed); -- 若干秒后到达目标位置

    LerpAngle() -- 360度内的角度插值

    MoveTowards(float current, float target, float maxDelta) -- 匀速运动
      常用newX = MoveTowards(x, target, Time.deltaTime * speed); 
      // 一秒移动speed米,加上负号即为反向移动

    PingPong(float t, float b) -- 往复运动
      返回值基于t的值的变化而变化,在0~b之间
      一般会使用PingPong(Time.time * speed, b); 匀速往返运动
      // 如果想要返回a~b怎么办? a + PingPong(speed, b-a); 即可

    任务28:Input输入类
    &29:(键盘按键 Getkey、
    &30:鼠标按键 GetMouseButton、
    &31&32:虚拟按键 GetButton/ GetAxis、触摸 GetTouch)
    &33:屏幕坐标系 mousePosition

    Input -- 键盘事件/鼠标事件/触摸屏事件

    静态变量:

    acceleration -- 重力感应

    gyro -- gyroscope 陀螺仪

    ime..... -- input method 输入法相关

    anyKey -- 当任意键key or mouse button按下时,即为true
    anyKeyDown -- is true when the first frame the user hits any key or mouse button

    mousePosition -- 鼠标在屏幕上的像素位置
      左下方为(0, 0, 0),可以为负值,表示在窗口外
      常用于Camera.main.screenPointToRay将位置转化为射线判断鼠标时候点到游戏物体
      详见Camera的方法

    静态方法:

    键盘按键:GetKey...(KeyCode key)/ GetKey...(string keyName)

    GetKey -- 键盘按键,被按下时会一直return true

    GetKeyDown/ GetKeyUp -- 按下/ 抬起瞬间

    void Update () {
        // GetKey
        if (Input.GetKeyDown(KeyCode.Space)) {
            // 只会在按下瞬间被触发
            print("Get Space Down");
        }
        if (Input.GetKeyUp(KeyCode.Space)) {
            // 只会在抬起瞬间被触发
            print("Get Space Up");
        }
        if (Input.GetKey(KeyCode.Space)) {
            // 按下时一直触发
            print("Get Space");
        }
        if (Input.GetKeyDown("left shift")) {
            print("shift");
        }

    鼠标按键 -- GetMouseButton...(int button)

    int button: 0 for left button, 1 for right button, 2 for middle button

    GetMouseButton()

    GetMouseButtonDown()/ GetMouseButtonUp()

    虚拟按键 -- GetButton...(String buttonName)

    在InputManager中定义的即为虚拟按键

    Input Manager设置:Edit -> Project Settings -> Input
      alt button: alternative button, 备用物理按键

    好处:1. 一个虚拟按键可以对应多个物理按键
          2. 名字很形象

    GetButton()/ GetButtonDown()/ GetButtonUp()

    GetAxis(string axisName) -- 返回该轴上的对应值(-1~1)

    常用cube.Translate(Vector3.forward * Time.deltaTime * Input.GetAxis("Horizontal"));

    返回值是渐变效果的,所以是非匀速运动,更圆滑

    GetAxisRaw(...) -- 返回的是-1/0/1 -- 返回值确定,是匀速运动,更灵敏

    触摸操作 -- GetTouch

    多指触摸 -- 好用的插件 easytouch

    任务34&35&36&37:Vector2

    Vector2:二维向量,或二维点坐标 -- 上下左右,少了z轴
      Vector2和Vector3是struct,而不是继承MonoBehaviour的类
      就像transform.position.x不能被直接赋值修改一样,是值类型,要整体赋值
      修改的时候需要Vector3 pos = transform.position; pos.x = ...; transform.position = pos;

    静态变量:

    down/ left/ right/ up/ zero (0,0)/ one (1,1)

    变量:

    magnitude -- 向量的长度

    sqrMagnitude -- 向量长度的平方,即x^2+y^2
      用处:比如在比较两个向量的长度时,使用sqr会减少性能的消耗

    normalized -- 该向量的单位化(长度变为1)

    x/y/ this[int] -- 向量的各值 this[0] == x; this[1] == y

    Constructors:

    new Vector2(x,y);

    方法:

    Equals() -- 两个Vector2值相同的时候,返回true
    也可以直接使用 == 来进行比较

    Normalize() -- 单位化该向量

    Set(newX, newY) -- 或者直接进行赋值也可以

    静态方法:

    Angle(Vector2 from, Vector2 to) -- 取得两个向量之间的夹角

    ClampMagnitude(Vector2 vector, float maxLength) -- 将向量的长度限定在maxLength之内

    Distance(Vector2 a, Vector2 b) -- 即(a-b).magnitude

    Dot() -- dot product

    Lerp(Vector2 a, Vector2 b, float t) -- 差值

    LerpUnclamped(Vector2 a, Vector2 b, float t) -- 差值,差别在于t<0或t>1时不会返回a或b,而是继续缩小/ 扩大

    Vector2 v1 = new Vector2(2, 2);
    Vector2 v2 = new Vector2(3, 4); 
    print(Vector2.Lerp(v1, v2, 0.5f)); // ((2+3)/2, (2+4)/2)
    print(Vector2.LerpUnclamped(v1, v2, 0.5f));  // ((2+3)/2, (2+4)/2)
    
    print(Vector2.Lerp(v1, v2, 2f)); // v2
    print(Vector2.LerpUnclamped(v1, v2, 2f));  // t大于1时便继续按比例扩大 (4,6)

    Max/ Min(Vector2 a, Vector2 b)

    MoveTowards(Vector2 a, Vector2 b, float maxDistanceDelta) -- 对x, y分别进行运算(匀速)

    任务38:Vector3

    静态变量:

    相比Vector2而言,Vector3多了z轴,所以多了两个方向 forward和 back

    方法:

    Cross(Vector3 lhs, Vector3 rhs) -- cross product 叉乘
      叉乘的结果方向由左手法则确定,大拇指为a,食指为b,中指即为结果方向

    Distance()
    Dot()
    Lerp()
    LerpUnclamped()
    Max/ Min()
    MoveTowards()
    Normalize()

    OrthoNormalize(Vector3 normal, Vector3 tangent) -- Normalize normal, Normalize tangent,   makes sure tangent is orthogonal (90 degrees) to normal.

    Project(Vector3 v, Vector3 onNormal) -- v对onNormal做投影后的结果向量

    ProjectOnPlane(Vector3 v, Vector3 planeNormal) -- v对planeNormal所表示的平面做投影

    Reflect(Vector3 inDirection, Vector3 inNormal) -- 返回入射光inDirection对于由inNormal所确定   的镜子平面的反射光

    Slerp(Vector3 a, Vector3 b, float t) -- spherically interpolates btw a and b (a and b are treated   as directions rather than points); the direction of the returned vector is interpolated by the   angle and its magnitude is interpolated btw the magnitudes of a and b
      常用于角色的转向

    SlerpUnclamped()

    任务39:对向量的加减乘除操作

    + :(a+b)即向量相接(可以用来做方向的叠加)

    - :(a-b)即b指向a的向量(可以用来指向)
      比如敌人指向Player的向量,就用Player的坐标所在向量-敌人的坐标所在向量)

    * :*数字 即magnitude变大,方向不变

    / :/数字 即magnitude变小,方向不变

    == :三个值相对相同

    任务40&41:使用Random生成随机数

    暴击率或是爆率等

    静态变量:

    value -- 0.0~1.0之间的随机小数 == Range(0, 1f); (0.0 and 1.0 are inclusive)

    state -- 当前seed的状态。可以被使用来保存当前随机数生成期的状态。比如使用seed1生成了三个随机数后,使用
      Random.State oldState = Random.state; 保存当前状态,之后若是想要继续在该seed1下生成随机数,
      只需使用Random.state = oldState; 即可继续生成seed1下的第四个随机数了。

    rotation -- 随机得到一个朝向

    insideUnitCircle -- 按照半径为1,圆心为(0,0)的一个圆,随机生成一个在圆内的二维坐标
      可以赋值给Vector3的变量,只会改变x和y的值

    insideUnitSphere -- 按照半径为1,圆心为(0,0)的一个球,随机生成一个在球内的三维坐标

    InitState(int seed) -- 初始化状态
      计算机产生的是伪随机数,是通过一系列计算生成的随机数,不是真正的随机数。seed就是生成随机数的算法需要的一个参数。
      该方法目的是给生成随机数的算法(如Range)提供seed。一些情况下,如果要求生成的随机数有一定规律(比如Debug的时候),
      就提供确定的seed;如果要求生成不确定的随机数,可以选择时间作为seed:
        Random.InitState((int)System.DateTime.Now.Ticks);
      注意,不设置InitState()的时候,Unity也会自动设置,保证 每次游戏运行时Range生成的随机数有规律可循

    Range(int min, int max) -- 返回min~max之间的整数(不包含max)
    Range(float min, float max) -- 返回min~max之间的小数(包含max)

    ColorHSV(hueMin, hueMax, saturationMin, saturationMax, valueMin, valueMax, alphaMin, alphaMax) -- Generates a random color from HSV and alpha ranges

    任务42&43&44:Quaternion四元数

    四元数:w, x, y, z 四个值组成一个四元数,表示一个物体的旋转

    欧拉角 Rotation: 三维向量Vector3,通过x, y, z三个值确定一个物体的旋转
      注意,围绕y轴旋转时,是按照世界坐标中的y轴而不是局部坐标系的y轴旋转
      而围绕x或z轴旋转是围绕自身的x或z轴进行旋转

    优劣:四元数在进行旋转的计算时更方便,而欧拉角在进行肉眼观察时更直观

    没有旋转时,默认的EulerAngles=(0, 0, 0); 默认的Rotation=(0, 0, 0, 1);
    围绕x轴旋转90°时,默认的EulerAngles=(90, 0, 0); 默认的Rotation=(0.7, 0, 0, 0.7);

    Quaternion中的方法:

    Euler() -- 把一个EulerAngle变换成一个四元数
      cube.rotation = Quaternion.Euler(new Vector3(45, 45, 45));

    变量.eulerAngles -- 把一个四元数转换成一个欧拉角:
      cube.rotation.eulerAngles;

    LookRotation(Vector3 forward, Vector3 upwards=Vector3.up) -- 返回一个Quaternion,表示主
      角的朝向;用来使主角面向敌人,使主角的z轴正方向与传递进来的forward方向保持一致

    案例:
      创建Plane作为Floor;
      创建Capsule作为Player,Capsule的z轴方向即Player朝向
      创建Cube作为Player的子物体,放在z轴正方向突出作为眼睛
      创建Cylinder作为Enemy
      Vector3 dir = enemy.position - player.position;  // player指向enemy的向量
      player.rotation = Quaternion.LookRotation(dir);// 将Vector3转换成四元数,并赋值
    问题:若Enemy与Player不在同一高度上,则Player会弯腰或抬头,事实上不需要
    解决:
      dir.y = 0; 即可
    问题:需要让Player慢慢转向而不是一下子就转向
    解决:
      使用Quaternion.Slerp()
        (如果是使用Lerp(),则是分别对w,x,y,z四个值分别做差值运算;
        Slerp()是适合做旋转的;
        但是Lerp()旋转较快,looks worse if the rotations are far apart
        可以这么理解,Lerp()是按直线变化过去的,而Slerp()是按圆弧变化过去的)
      Quaternion target = Quaternion.LookRotation(dir);
      player.rotation = Quaternion.Slerp(player.rotation, target, Time.deltaTime*speed);

    private bool isRotating = false;
    private Vector3 dir;
    private float speed = 2f;
    private void Update() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            isRotating = true;
            dir = enemy.position - player.position;// player指向enemy的向量
            dir.y = 0;
        }
        if (isRotating) {
            player.rotation = Quaternion.Slerp(player.rotation,     
                Quaternion.LookRotation(dir), Time.deltaTime * speed);
            // 将Vector3转换成一个四元数,并赋值)
        }
    }

    任务45:Rigidbody刚体中的position和MovePosition控制位置
    46:rotation和MoveRotation控制旋转
    47:通过AddForce控制运动

    控制位置:

    position -- If you change the position of a Rigidbody using Rigidbody.position, the transform   will be updated after the next physics simulation step. This is faster than updating the   position using Transform.position as the latter will cause all attached Colliders to   
      recalculate their positions relative to the Rigidbody.

      playerRgd.position = playerRgd.position + Vector3.forward * Time.deltaTime;

      If you want to continuously move a rigidbody, use MovePosition() instead, which
      takes interpolation into account. If you want to teleport a rigidbody from one position to   another, with no intermediate positions being rendered.

    MovePosition(Vector3 position) -- Moves the rigidbody to position.

      playerRgd.MovePosition(playerRgd.position + Vector3.forward * Time.deltaTime);

    控制旋转

    rotation -- 和position相似,faster than Transform.rotation, 否则Collider范围发生了改变,需要
      重新进行计算,所以Transform的改变会更耗费性能

    MoveRotation() -- 持续转向操作就使用MoveRotation(),一次性转向操作就直接修改rotation

    public Rigidbody playerRgd;
    public Transform enemy;
    
    private bool isRotating = false;
    private Vector3 dir;
    private float speed = 2f;
    
    void Update () {
        // playerRgd.position = playerRgd.position+Vector3.forward*Time.deltaTime;
        playerRgd.MovePosition(playerRgd.position+Vector3.forward*Time.deltaTime);
        if (Input.GetKeyDown(KeyCode.Space)) {
            isRotating = true;
            dir = enemy.position - playerRgd.position;// player指向enemy的向量
            dir.y = 0;
        }
        if (isRotating) {
            // 将Vector3转换成一个四元数,并赋值)
            playerRgd.rotation = Quaternion.Slerp(playerRgd.rotation, 
                Quaternion.LookRotation(dir), Time.deltaTime * speed);
    
            if (playerRgd.rotation.Equals(dir)) {
                isRotating = false;
            }
        }
    }

    施加外力:

    AddForce() -- 运动为加速/减速过程
    注:力太小的时候也可能不运动

    任务48:Camera类

    默认的Camera的tag="MainCamera"

    实例:点击鼠标,用射线的方法判断是否点击了某个GameObject

    1. 得到Camera组件
      a. 查找游戏物体:GameObject.Find("MainCamera").GetComponent<Camera>();
      b. 通过静态变量main获取到标签为"MainCamera"的游戏物体的Camera组件:
        Camera.main;
    2. 将鼠标点击转化为射线

    void Update () {
        // 将屏幕上的点Input.mousePosition转换为一个射线
        Ray ray = camera.ScreenPointToRay(Input.mousePosition);
        RaycastHit hit;
        // 检测是否碰撞到,将结果储存在hit中
        bool isCollidered = Physics.Raycast(ray,out hit);
        if(isCollidered) {
            Debug.Log(hit.collider);
            Debug.DrawRay(ray.origin, ray.direction * 100, Color.red);
        }
    }

    任务49:通过Application获取datapath

    Application类:

    datapath -- 数据路径:在不同平台下返回值不同
      Unity Editor: <path to project folder>/Assets
      Mac player: <path to player app bundle>/Contents
      iOS player: <path to player app bundle>/<AppName.app>/Data
      Win/Linux player: <path to exe_Data folder>/
      ...

    persistentDataPath -- A directory path where data expected to be kept between runs can be
      stored. When publishing on iOS and Android, persistentDataPath will point to a public
      directory on the device. Files in this location won't be erased with each update of the
      app. When you build the app, a GUID will be generated based on the Bundle Identifier,
      and this GUID will be part of persistentDataPath. If you keep the same Bundle Identifier
      in future versions then the app will continue accessing the same location on every
      update.

    streamingAssetsPath -- 流资源文件:在Project目录下新建一个StreamingAssets文件夹,该文
      件夹在游戏安装好后是单独存在的,而其他文件夹下的资源会统一被打包成资源包(Most
      assets in Unity are combined into the project when it is built. However, it is sometimes
      useful to place files into the normal filesystem on the target machine to make them
      accessible via a pathname. Any files placed in a folder called StreamingAssets in a Unity
      project will be copied verbatim to a particular folder on the target machine. You can
      retrieve the folder using the Application.streamingAssetsPath property.)
      On MacOS or Windows: path = Application.dataPath + "/StreamingAssets"
      On iOS: path = Application.dataPath + "/Raw"
      On Android: path = "jar:file://" + Application.dataPath + "!/assets/"

    temporaryCachePath -- 临时文件目录

    print(Application.dataPath); // 工程路径
    print(Application.streamingAssetsPath); // 流文件路径
    print(Application.persistentDataPath); // 存储文件路径(持久化)
    print(Application.temporaryCachePath); // 临时文件路径

    任务50:Application中的常用变量和方法

    companyName:Build Settings->Player Settings中的Company Name

    identifier:Android上的'package' 包名;Build Settings->Player Settings中的Bundle Identifier

    installerName:安装包名

    productName:游戏名Build Settings->Player Settings中的Product Name

    isEditor:判断是否是在Unity Editor模式下运行

    isFocused:判断用户当前是否将焦点处于该游戏

    isMobilePlatform:判断当前是否在已知的移动设备上运行

    isPlaying:判断是否in any kind of player or in Unity editor's playmode

    platform: 返回一个RuntimePlatform类型的值,如RuntimePlatform.WindowsPlayer
      某段代码只在某些特定平台下运行时可使用

    runInBackground:default is false;是否允许游戏在后台运行(无法在Android/ iOS上起作用)

    version:Build Settings->Player Settings中的Version

    OpenURL(string url) -- 在浏览器中打开url

    Quit() -- 退出程序,注:在Editor模式下无效,需要设UnityEditor.EditorApplication.isPlayer = false;

    if (Input.GetKeyDown(KeyCode.Escape)) {
        if (Application.isEditor) {
            UnityEditor.EditorApplication.isPlaying = false;
        } else {
            Application.Quit(); // 编辑器模式下无效
        }
    }

    CaptureScreenshot(string screenshotName) -- 游戏截图
      已弃用,现在使用ScreenCapture.CaptureScreenshot(string)

    任务51&52:SceneManager

    之前加载场景的方法被放在Application中,如Application.LoadLevel(index) -- 否决的

    新的类:UnityEngine.SceneManagerment.SceneManager

    变量:

    sceneCount -- 当前loaded的场景的总数

    方法:

    LoadScene() -- 加载场景
      public static void LoadScene(int sceneBuildIndex, SceneManagement.LoadSceneMode mode);
      public static void LoadScene(string sceneName, SceneManagement.LoadSceneMode mode);
      LoadSceneMode默认值为LoadSceneMode.Single,表示销毁当前场景的游戏物体,载入新场景|
      LoadSceneMode.Additive表示 Additive loads a Scene which appears in the Hierarchy window while another is active.

    LoadSceneAsync() -- 异步加载场景
      LoadScene()在加载场景时如果加载场景时间需要很久,则选择LoadSceneAsync()
      LoadSceneAsync()会返回一个AsyncOperation类的值,存储progress、isDone等信息
      可以在加载页面显示进度条、提示信息等等

    CreateScene() -- Create an empty new Scene at runtime with the given name. The new Scene will be opened additively into the hierarchy alongside any existing Scenes that are currently open. For Edit-time creation, use EditorSceneManager.NewScene()

    GetActiveScene() -- 返回当前场景的Scene对象

    GetSceneAt(index) -- 返回该index(0~sceneCount已加载场景) 的Scene对象(可以获取该Scene的某些属性)

    GetSceneByBuildIndex(index) -- 与GetSceneAt的区别在于这里的index是Build Setting中的index,并且该Scene是loaded的状态

    GetSceneByName(string name)/ GetSceneByPath(string scenePath) -- 前提也是该Scene是loaded的状态

    SetActiveScene(Scene scene) -- Set the scene to be active. Returns false if the Scene is not loaded yet.
      The active Scene is the Scene which will be used as the target for new GameObjects instantiated by scripts. 

    Event:

    activeSceneChanged -- 当activeScene改变时

    sceneLoaded -- 当有新scene被载入时

    sceneUnloaded -- 当有scene被卸载时

    void Start () {
        SceneManager.activeSceneChanged += OnActiveSceneChanged;
        SceneManager.sceneLoaded += OnSceneLoaded;
    }
    
    private void OnActiveSceneChanged(Scene a, Scene b) {
        print("OnActiveSceneChanged:" + a.name + " to " + b.name);
        // 这里a scene的name无法获取到,因为此时a不是loaded状态
    }
    
    private void OnSceneLoaded(Scene a, LoadSceneMode mode) {
        print("OnSceneLoaded:" + a.name + " on " + mode);
        // OnSceneLoaded的执行会在OnActiveSceneChanged之后
    }

    任务53:课程结束语

    其他API的讲解会在Unity API常用方法和类详细讲解(下)中继续讲解。

     

  • 相关阅读:
    行列式的六条运算法则整理
    李昊大佬的CV模板
    洛谷P2918 [USACO08NOV]买干草(一道完全背包模板题)
    .
    洛谷P2822 组合数问题
    欧拉筛法模板&&P3383 【模板】线性筛素数
    拓展欧几里得算法
    欧几里得算法求最大公约数模板
    P2678 跳石头
    【五一qbxt】test1
  • 原文地址:https://www.cnblogs.com/FudgeBear/p/8459993.html
Copyright © 2011-2022 走看看