喵的Unity游戲開發(fā)之路 - 互動(dòng)環(huán)境(有影響的運(yùn)動(dòng))

如圖片空幻、視頻或代碼格式等顯示異常憎茂,請(qǐng)查看原文:

https://mp.weixin.qq.com/s/Sv0FOxZCAHHUQPjT8rUeNw

????????很多童鞋沒有系統(tǒng)的Unity3D游戲開發(fā)基礎(chǔ)侨嘀,也不知道從何開始學(xué)榜跌。為此我們精選了一套國(guó)外優(yōu)秀的Unity3D游戲開發(fā)教程郭宝,翻譯整理后放送給大家重虑,教您從零開始一步一步掌握Unity3D游戲開發(fā)。?本文不是廣告廷支,不是推廣频鉴,是免費(fèi)的純干貨!本文全名:喵的Unity游戲開發(fā)之路 - 移動(dòng)?- 互動(dòng)環(huán)境?- 有影響的運(yùn)動(dòng)





通過加速區(qū)創(chuàng)建跳板和懸浮力。

制作一個(gè)多功能檢測(cè)區(qū)。

反應(yīng)性地交換材料并激活或停用對(duì)象攒暇。

通過事件觸發(fā)的簡(jiǎn)單插值移動(dòng)對(duì)象。



這是有關(guān)控制角色移動(dòng)的教程系列的第十期周荐。它使環(huán)境能夠以各種方式對(duì)運(yùn)動(dòng)做出反應(yīng)。


本教程使用Unity 2019.4.4f1制作悯姊。它還使用ProBuilder軟件包羡藐。

效果之一

修正

我改進(jìn)了軌道攝像機(jī)的1.4節(jié)“使焦點(diǎn)居中”,以便更好地實(shí)現(xiàn)焦點(diǎn)居中和焦點(diǎn)半徑限制的相互作用悯许。調(diào)整OrbitCamera.UpdateFocusPoint如下:

  •   void UpdateFocusPoint () {    previousFocusPoint = focusPoint;    Vector3 targetPoint = focus.position;    if (focusRadius > 0f) {      float distance = Vector3.Distance(targetPoint, focusPoint);      float t = 1f;      if (distance > 0.01f && focusCentering > 0f) {        t = Mathf.Pow(1f - focusCentering, Time.unscaledDeltaTime);      }      if (distance > focusRadius) {        t = Mathf.Min€(t, focusRadius / distance);      }      focusPoint = Vector3.Lerp(targetPoint, focusPoint, t);    }    else {      focusPoint = targetPoint;    }  }

    我還更改了“移動(dòng)地面”部分2.3確定運(yùn)動(dòng),因此忽略了質(zhì)量較輕的連接物體辉阶。這樣可以防止球體自動(dòng)跟隨其推開的輕物體先壕。如下調(diào)整MovingSphere.UpdateState結(jié)尾:

  •     if (connectedBody) {      if (connectedBody.isKinematic || connectedBody.mass >= body.mass) {        UpdateConnectionState();      }    } 
    
    

    最后,更改了“攀爬”第2.5節(jié)的“可選攀登”谆甜,以防止自動(dòng)粘在動(dòng)畫的可攀爬表面上垃僚。這是通過在EvaluateCollision中調(diào)節(jié)desiresClimbing而不是在Climbing屬性中完成的:

  •   bool Climbing =>climbContactCount > 0 && stepsSinceLastJump > 2;
    void EvaluateCollision (Collision collision) { if ( desiresClimbing &&upDot >= minClimbDotProduct && (climbMask & (1 << layer)) != 0 ) { climbContactCount += 1; climbNormal += normal; lastClimbNormal = normal; connectedBody = collision.rigidbody; } }

    (另一個(gè))效果








    加速區(qū)



    主動(dòng)環(huán)境比靜態(tài)環(huán)境有趣,尤其是當(dāng)它對(duì)正在發(fā)生的事情做出反應(yīng)時(shí)规辱。這種行為可以對(duì)任何事情做出反應(yīng)谆棺,也可以做任何事情,但是一個(gè)簡(jiǎn)單的例子就是跳墊:只要有東西落在墊上,它就會(huì)向上發(fā)射改淑。這可能是我們的運(yùn)動(dòng)球或碰巧掉落或推到墊子上的任何其他物體碍岔。因此,該行為在邏輯上屬于跳板朵夏。其他物體不必知道它的存在蔼啦,它們只是突然結(jié)束飛行。



    區(qū)域組成



    描述跳板行為的最通用方法是仰猖,該區(qū)域可加速進(jìn)入其的任何物體捏肢。因此,我們將創(chuàng)建一個(gè)AccelerationZone組件類型饥侵,其可配置的速度不能為負(fù)鸵赫。


    
    

  • using UnityEngine;
    public class AccelerationZone : MonoBehaviour {
    [SerializeField, Min(0f)] float speed = 10f;}


    可以通過將具有觸發(fā)對(duì)撞器的對(duì)象添加到場(chǎng)景,然后將區(qū)域行為附加到場(chǎng)景來創(chuàng)建區(qū)域躏升。您也可以添加可視化跳板的對(duì)象奉瘤,但是我只是用半透明的黃色材料使該區(qū)域可見。



    當(dāng)帶Rigidbody的東西進(jìn)入?yún)^(qū)域時(shí)煮甥,我們應(yīng)該加速它盗温。為此在OnTriggerEnter添加一個(gè)方法Accelerate,該方法以觸發(fā)主體作為參數(shù)調(diào)用新方法成肘。進(jìn)入該區(qū)域的所有物體都會(huì)發(fā)生這種情況卖局,但是如果需要,您可以使用圖層來防止檢測(cè)到某些物體双霍。


  • void OnTriggerEnter (Collider other) {    Rigidbody body = other.attachedRigidbody;    if (body) {      Accelerate(body);    }  }
    void Accelerate(Rigidbody body) {}


    Accelerate中只需使身體速度的Y分量等于配置的速度砚偶,除非它已經(jīng)更大。其他速度分量不受影響洒闸。


  • void Accelerate(Rigidbody body) {    Vector3 velocity = body.velocity;    if (velocity.y >= speed) {      return;    }
    velocity.y = speed; body.velocity = velocity; }





    防止猛然掉地



    當(dāng)發(fā)射常規(guī)物體時(shí)染坯,這種簡(jiǎn)單的方法效果很好,但是我們的球體沒有正確發(fā)射丘逸。當(dāng)它進(jìn)入?yún)^(qū)域時(shí)单鹿,它似乎獲得了很大的前進(jìn)速度。發(fā)生這種情況是因?yàn)槲覀儗⑵淇ㄔ诹说厣仙罡佟T谶@種情況下仲锄,可以通過降低“ 最大捕捉速度”來解決,但不適用于設(shè)置為低速的加速區(qū)域湃鹊。為了防止接地儒喊,一般來說,我們必須指示MovingSphere暫時(shí)不要執(zhí)行接地币呵。我們可以通過PreventSnapToGround向其添加設(shè)置stepsSinceLastJump為-1 的公共方法來做到這一點(diǎn)怀愧。


  • public void PreventSnapToGround () {    stepsSinceLastJump = -1;  }


    現(xiàn)在AccelerationZone.Accelerate可以在主體具有MovingSphere組件的情況下調(diào)用此方法,我們可以通過調(diào)用TryGetComponent球體作為輸出參數(shù)來進(jìn)行檢查和檢索。


  •   void Accelerate(Rigidbody body) {    if (body.TryGetComponent(out MovingSphere sphere)) {      sphere.PreventSnapToGround();    }  }



    請(qǐng)注意芯义,這種方法不會(huì)重置跳躍階段哈垢,因此在沒有降落的情況下彈跳跳板不會(huì)刷新空氣跳躍。




    持續(xù)加速



    瞬時(shí)速度變化對(duì)于跳板很合適毕贼,但是我們也可以使用該區(qū)域創(chuàng)建其他連續(xù)的加速度現(xiàn)象温赔,例如懸浮區(qū)域。我們可以通過簡(jiǎn)單地添加與OnTriggerStay相同的方法OnTriggerEnter來支持這一點(diǎn)鬼癣。


  • void OnTriggerStay (Collider other) {    Rigidbody body = other.attachedRigidbody;    if (body) {      Accelerate(body);    }  }


    如果效果持續(xù)時(shí)間較長(zhǎng)陶贼,那么通過適當(dāng)?shù)募铀俣葋韺?shí)現(xiàn)速度變化會(huì)更好一些,因此讓我們向該區(qū)域添加一個(gè)可配置的加速度待秃,且最小值也應(yīng)為零拜秧。如果將其設(shè)置為零,我們將立即進(jìn)行更改章郁,否則將應(yīng)用加速枉氮。


  •   [SerializeField, Min(0f)]  floatacceleration = 10f,speed = 10f;

    void Accelerate(Rigidbody body) {
    if (acceleration > 0f) { velocity.y = Mathf.MoveTowards( velocity.y, speed, acceleration * Time.deltaTime ); } else { velocity.y = speed; } }



    也可以施加力,這樣質(zhì)量較大的物體最終的加速度會(huì)變慢暖庄,但是固定的加速度使水平設(shè)計(jì)更容易聊替,因此我使用了這一點(diǎn)。




    任意方向



    最后培廓,為了使其可以向任何方向加速惹悄,請(qǐng)?jiān)贏ccelerate開始時(shí)將人體速度轉(zhuǎn)換為區(qū)域的局部空間,并在應(yīng)用時(shí)將其轉(zhuǎn)換回世界空間肩钠。通過InverseTransformDirection和TransformDirection這樣做泣港,因此區(qū)域的比例不會(huì)對(duì)其產(chǎn)生影響。現(xiàn)在可以通過旋轉(zhuǎn)區(qū)域來控制加速方向价匠。


  •   void Accelerate(Rigidbody body) {    Vector3 velocity =transform.InverseTransformDirection(body.velocity);
    body.velocity =transform.TransformDirection(velocity); }






    對(duì)存在做出反應(yīng)



    加速區(qū)只是如何創(chuàng)建具有特定行為的觸發(fā)區(qū)的一個(gè)示例当纱。如果您需要一個(gè)區(qū)域執(zhí)行其他操作,則必須為其編寫新代碼踩窖。但是檢測(cè)和響應(yīng)某處某物的存在的簡(jiǎn)單行為是如此普遍坡氯,以至于我們理想情況下只編寫一次。而且許多行為非常簡(jiǎn)單(例如激活對(duì)象)毙石,以至于無法為其創(chuàng)建專用的組件類型廉沮。而更復(fù)雜的行為通常只是一些簡(jiǎn)單動(dòng)作的組合。如果關(guān)卡設(shè)計(jì)師可以通過簡(jiǎn)單地配置游戲?qū)ο蟛⑻砑右恍┙M件來創(chuàng)建它徐矩,而不必一直創(chuàng)建專門的代碼,這將很方便叁幢。


    檢測(cè)區(qū)



    讓我們從創(chuàng)建一個(gè)DetectionZone組件開始滤灯,該組件檢測(cè)在其區(qū)域中是否存在某些東西,并在有東西進(jìn)入或退出時(shí)通知感興趣的人。我們通過給它配置的UnityEvent類型的字段onEnter及onExit鳞骤,從UnityEngine.Events命名空間窒百。


    
    

    using UnityEngine;using UnityEngine.Events;
    public class DetectionZone : MonoBehaviour {
    [SerializeField] UnityEvent onEnter = default, onExit = default;}



      void OnTriggerEnter (Collider other) {    onEnter.Invoke();  }
    void OnTriggerExit (Collider other) { onExit.Invoke(); }

    
    


    檢查器會(huì)將組件的事件作為名為On Enter()On Exit()的列表公開,這些列表最初是空的豫尽。名稱后面的括號(hào)中沒有任何內(nèi)容篙梢,表示這些事件沒有參數(shù)。






    材料選擇器



    為了演示這是如何工作的美旧,我們將創(chuàng)建一個(gè)簡(jiǎn)單的MaterialSelector組件類型渤滞,該組件類型具有可配置的材料和MeshRenderer參考數(shù)組。它具有一個(gè)帶有index參數(shù)的Select公共方法榴嗅,該方法將有效的材質(zhì)分配給渲染器(如果有效)妄呕。


    
    
    using UnityEngine;
    public class MaterialSelector : MonoBehaviour {
    [SerializeField] Material[] materials = default;
    [SerializeField] MeshRenderer meshRenderer = default;
    public void Select (int index) { if ( meshRenderer && materials != null && index >= 0 && index < materials.Length ) { meshRenderer.material = materials[index]; } }}


    創(chuàng)建一個(gè)帶有紅色非活動(dòng)區(qū)域和綠色活動(dòng)區(qū)域的材質(zhì)選擇器組件,這些組件將用于更改檢測(cè)區(qū)域的可視化嗽测。盡管不需要將其添加到受影響的游戲?qū)ο笾行骼@是最有意義的。



    現(xiàn)在唠粥,通過按項(xiàng)目的+按鈕將其添加到檢測(cè)區(qū)域組件的輸入事件列表中疏魏。通過材質(zhì)選擇器的左下角字段將游戲?qū)ο箧溄拥皆擁?xiàng)目。之后晤愧,可以選擇MaterialSelector.Select方法大莫。由于此方法具有整數(shù)參數(shù),因此其值將顯示在方法名稱下方养涮。默認(rèn)情況下葵硕,它設(shè)置為零,表示無效狀態(tài)贯吓,因此將其設(shè)置為1懈凹。然后對(duì)退出事件執(zhí)行相同的操作,這次將參數(shù)保留為零悄谐。



    確保默認(rèn)情況下介评,區(qū)域?qū)ο笫褂貌换顒?dòng)的紅色材料。然后以這種方式開始爬舰,但是一旦有物體進(jìn)入?yún)^(qū)域们陆,它將切換為活動(dòng)的綠色材料。當(dāng)有東西離開該區(qū)域時(shí)情屹,它將再次變?yōu)榧t色坪仇。





    首次進(jìn)入和最后退出



    該檢測(cè)區(qū)域可以工作,但確實(shí)可以完成其編程的工作垃你,即每次進(jìn)入時(shí)調(diào)用一次進(jìn)入椅文,每次離開時(shí)調(diào)用一次退出喂很。因此,我們可以混合使用enter和exit事件(例如enter皆刺,enter少辣,exit,enter羡蛾,exit漓帅,exit),并且當(dāng)其中仍然有東西時(shí)痴怨,最終會(huì)出現(xiàn)視覺上無效的區(qū)域忙干。在區(qū)域中保持活動(dòng)狀態(tài)時(shí),使區(qū)域保持活動(dòng)狀態(tài)更加直觀腿箩。使用保證進(jìn)入和退出事件將嚴(yán)格交替的區(qū)域進(jìn)行設(shè)計(jì)也更加容易豪直。因此,它僅應(yīng)在第一件東西進(jìn)入時(shí)和最后一件東西離開時(shí)發(fā)出信號(hào)珠移。將事件重命名為onFirstEnter弓乙,并將onLastExit其重命名以使其變得清晰,這將需要再次掛接事件钧惧。



    為了使這種行為成為可能暇韧,我們必須跟蹤區(qū)域中當(dāng)前的對(duì)撞機(jī)。我們將通過將DetectionZone命名空間中的List<Collider>字段初始化為System.Collections.Generic新列表來完成此操作浓瞪。


    using UnityEngine;using UnityEngine.Events;using System.Collections.Generic;
    public class DetectionZone : MonoBehaviour {
    [SerializeField] UnityEvent onFirstEnter = default, onLastExit = default;
    List<Collider> colliders = new List<Collider>(); }




    該列表如何工作懈玻?

    請(qǐng)參閱“ 對(duì)象管理”系列的“ 持久對(duì)象”教程。



    OnTriggerEnter中僅調(diào)用輸入事件如果列表為空乾颁,則始終對(duì)撞機(jī)添加到列表中涂乌,以保持它的軌道。


      void OnTriggerEnter (Collider other) {    if (colliders.Count == 0) {      onFirstEnter.Invoke();    }    colliders.Add(other);  }


    在這種情況下英岭,我們將在OnTriggerExit中從列表刪除對(duì)撞機(jī)湾盒,僅當(dāng)列表為空時(shí)才調(diào)用exit事件。列表的Remove方法返回刪除是否成功诅妹。應(yīng)當(dāng)總是這樣罚勾,因?yàn)榉駝t我們將無法跟蹤對(duì)撞機(jī),但是我們?nèi)匀豢梢詫?duì)其進(jìn)行檢查吭狡。


      void OnTriggerExit (Collider other) {    if (colliders.Remove(other) && colliders.Count == 0) {      onLastExit.Invoke();    }  }





    檢測(cè)出現(xiàn)和消失的對(duì)象



    不幸的是尖殃,OnTriggerExit它是不可靠的,因?yàn)樵谕S没螅没蜾N毀游戲?qū)ο蠡蚱鋵?duì)撞機(jī)時(shí)送丰,不會(huì)調(diào)用它。不應(yīng)該單獨(dú)禁用碰撞器弛秋,因?yàn)槟菢訒?huì)導(dǎo)致物體掉落到幾何體中蚪战,因此我們將不支持此功能牵现。但是我們應(yīng)該能夠處理整個(gè)游戲?qū)ο笤趨^(qū)域內(nèi)時(shí)被禁用或破壞的情況铐懊。


    每個(gè)物理步驟邀桑,我們都必須檢查區(qū)域中的對(duì)撞機(jī)是否仍然有效。添加一個(gè)在對(duì)撞機(jī)列表中循環(huán)的FixedUpdate方法科乎。如果對(duì)撞機(jī)進(jìn)行評(píng)估壁畸,false則意味著它或其游戲?qū)ο笠驯黄茐摹H绻皇沁@種情況茅茂,我們必須檢查其游戲?qū)ο笫欠褚淹S媚笃迹覀兛梢酝ㄟ^activeInHierarchy其游戲?qū)ο蟮膶傩詠聿檎摇H绻麑?duì)撞機(jī)不再有效空闲,請(qǐng)從列表中將其刪除令杈,并減少循環(huán)迭代器。如果列表為空碴倾,則調(diào)用exit事件逗噩。


    void FixedUpdate () {    for (int i = 0; i < colliders.Count; i++) {      Collider collider = colliders[i];      if (!collider || !collider.gameObject.activeInHierarchy) {        colliders.RemoveAt(i--);        if (colliders.Count == 0) {          onLastExit.Invoke();        }      }    }  }


    大多數(shù)情況下,檢測(cè)區(qū)域中可能沒有物體跌榔。為了避免不必要的FixedUpdate連續(xù)調(diào)用异雁,我們可以在喚醒組件時(shí)以及最后一個(gè)對(duì)撞機(jī)退出后禁用該組件趋箩。然后我們只有在有東西進(jìn)入后才啟用它莹捡。之所以有效,是因?yàn)闊o論是否啟用行為均蜜,總是會(huì)觸發(fā)觸發(fā)器方法担平。


    void Awake () {    enabled = false;  }
    void FixedUpdate () { for (int i = 0; i < colliders.Count; i++) { Collider collider = colliders[i]; if (!collider || !collider.gameObject.activeInHierarchy) { colliders.RemoveAt(i--); if (colliders.Count == 0) { onLastExit.Invoke(); enabled = false; } } } }
    void OnTriggerEnter (Collider other) { if (colliders.Count == 0) { onFirstEnter.Invoke(); enabled = true; } colliders.Add(other); }
    void OnTriggerExit (Collider other) { if (colliders.Remove(other) && colliders.Count == 0) { onLastExit.Invoke(); enabled = false; } }


    接下來示绊,我們還應(yīng)該處理區(qū)域游戲?qū)ο蟊旧肀煌S没蜾N毀的情況,因?yàn)楫?dāng)事件仍在區(qū)域中時(shí)發(fā)生時(shí)暂论,調(diào)用退出事件是有意義的面褐。我們都可以通過添加OnDisable清除列表并在列表不為空時(shí)調(diào)用exit事件的方法來做到。


    void OnDisable () {    if (colliders.Count > 0) {      colliders.Clear();      onLastExit.Invoke();    }  }


    請(qǐng)注意空另,檢測(cè)區(qū)的組件不應(yīng)由其他代碼禁用盆耽,因?yàn)樗梢怨芾碜约旱臓顟B(tài)。一般規(guī)則是不要禁用檢測(cè)區(qū)域組件扼菠,也不要禁用任何可能影響該區(qū)域的對(duì)撞機(jī)摄杂。這些游戲?qū)ο髴?yīng)全部停用或銷毀。




    熱裝



    因?yàn)闊嶂剌d(在編輯器播放模式下重新編譯)OnDisable將被調(diào)用循榆,因此它違反了我們剛剛聲明的規(guī)則析恢。這將導(dǎo)致調(diào)用退出事件以響應(yīng)熱重載,此后已存在于該區(qū)域中的對(duì)象將被忽略秧饮。幸運(yùn)的是映挂,我們可以檢測(cè)到OnDisable中的熱重裝泽篮。如果同時(shí)啟用了該組件并且游戲?qū)ο筇幱诨顒?dòng)狀態(tài),則我們將進(jìn)行熱重載柑船,并且什么也不做帽撑。當(dāng)游戲?qū)ο鬀]有被銷毀而組件被銷毀時(shí),情況也是如此鞍时,但是我們裁定不應(yīng)該這樣做亏拉。


    我們只需要在編輯器中播放時(shí)進(jìn)行檢查,就可以將代碼包裝在#if UNITY_EDITOR和中#endif逆巍。


      void OnDisable () {#if UNITY_EDITOR    if (enabled && gameObject.activeInHierarchy) {      return;    }#endif    if (colliders.Count > 0) {      colliders.Clear();      onLastExit.Invoke();    }  }




    OnDisable中相關(guān)的狀態(tài)組合是什么及塘?

    如果禁用了該組件,則將其禁用或禁用游戲?qū)ο笕窦缓笪覀兝^續(xù)進(jìn)行笙僚。否則,如果游戲?qū)ο笪刺幱诨顒?dòng)狀態(tài)灵再,則該游戲?qū)ο髮⒈煌S没蜾N毀肋层,然后我們繼續(xù)進(jìn)行。否則檬嘀,它要么是熱裝槽驶,要么是僅組件被破壞,我們將其忽略鸳兽。





    更復(fù)雜的行為



    這只是通過事件可以完成的簡(jiǎn)單演示掂铐。您可以通過向事件列表中添加更多條目來創(chuàng)建更復(fù)雜的行為。您不必為此創(chuàng)建新方法揍异,您可以使用現(xiàn)有方法全陨。限制是它必須是與事件的參數(shù)列表匹配的void方法或?qū)傩栽O(shè)置器,或者最多具有一個(gè)可序列化的參數(shù)衷掷。例如辱姨,我進(jìn)行了一些設(shè)置,以便在檢測(cè)區(qū)域內(nèi)有東西的同時(shí)關(guān)閉懸浮區(qū)域戚嗅,除了更改區(qū)域本身的可視化效果之外雨涛。



    您不必總是對(duì)所有事件都響應(yīng)。您可能只有在進(jìn)入或退出時(shí)才觸發(fā)某些事件懦胞。例如替久,在進(jìn)入?yún)^(qū)域時(shí)激活某些內(nèi)容。然后退出并不會(huì)取消激活它躏尉,而重新進(jìn)入則會(huì)再次激活它蚯根,這無濟(jì)于事。




    這種基于事件的方法可以用于整個(gè)游戲嗎胀糜?

    從理論上講颅拦,是的蒂誉,這對(duì)于快速制作原型非常有用,但是卻很麻煩距帅。一旦發(fā)現(xiàn)自己重復(fù)了一個(gè)復(fù)雜的模式右锨,就可以為其創(chuàng)建專用的方法或行為,這應(yīng)該更容易使用锥债,并在以后必要時(shí)進(jìn)行優(yōu)化陡蝇。





    簡(jiǎn)單運(yùn)動(dòng)



    我們將在本教程中介紹的最后一種情況是移動(dòng)環(huán)境對(duì)象。復(fù)雜的運(yùn)動(dòng)可以通過動(dòng)畫來完成哮肚,可以通過檢測(cè)區(qū)域觸發(fā)。但是通常兩點(diǎn)之間的簡(jiǎn)單線性插值就足夠了广匙,例如允趟,對(duì)于門,電梯或浮動(dòng)平臺(tái)鸦致。因此潮剪,讓我們添加對(duì)此的支持。



    自動(dòng)滑塊



    無論插值什么分唾,它在概念上都由從0到1的滑塊控制抗碰。如何更改值是與插值本身不同的問題。保持滑塊分離還可以將其用于多個(gè)插值绽乔。因此弧蝇,我們將創(chuàng)建一個(gè)AutomaticSlider專用于此值的組件。它的可配置持續(xù)時(shí)間必須為正折砸。當(dāng)我們使用它為物理對(duì)象設(shè)置動(dòng)畫時(shí)看疗,我們將使其在FixedUpdate方法中增加其值,并確保它不會(huì)過沖睦授。一旦值達(dá)到1两芳,我們就可以完成并可以禁用滑塊。


    
    
    using UnityEngine;
    public class AutomaticSlider : MonoBehaviour {
    [SerializeField, Min(0.01f)] float duration = 1f;
    float value;
    void FixedUpdate () { value += Time.deltaTime / duration; if (value >= 1f) { value = 1f; enabled = false; } }}


    再一次去枷,我們將使用Unity事件來將行為附加到滑塊怖辆。在這種情況下,我們需要一個(gè)on-value-changed事件删顶,該事件將用于傳遞滑塊的當(dāng)前值竖螃。因此,我們的事件需要一個(gè)float參數(shù)翼闹,我們可以為其使用UnityEvent<float>類型斑鼻。在FixedUpdate結(jié)束時(shí)調(diào)用事件。


    using UnityEngine;using UnityEngine.Events;
    public class AutomaticSlider : MonoBehaviour { [SerializeField] UnityEvent<float> onValueChanged = default;
    float value;
    void FixedUpdate () { onValueChanged.Invoke(value); }}


    但是猎荠,Unity無法序列化通用事件類型坚弱,因此該事件不會(huì)顯示在檢查器中蜀备。我們必須創(chuàng)建自己的具體可序列化事件類型,該事件類型可以簡(jiǎn)單地?cái)U(kuò)展UnityEvent<float>荒叶。此類型特定于我們的滑塊碾阁,因此通過在類內(nèi)部以及事件字段本身進(jìn)行聲明將其設(shè)置為嵌套類型。


    [System.Serializable]  public class OnValueChangedEvent : UnityEvent<float> { }
    [SerializeField] OnValueChangedEventonValueChanged = default;


    進(jìn)入播放模式時(shí)些楣,滑塊將立即開始增加脂凶。如果您不希望這樣做,請(qǐng)?jiān)谀J(rèn)情況下將其禁用愁茁。然后蚕钦,您可以將其連接到檢測(cè)區(qū)域,以在以后啟用它鹅很。



    請(qǐng)注意嘶居,在這種情況下,事件的名稱后跟(Single)促煮,表示它具有一個(gè)參數(shù)邮屁。Single是指float類型,它是單精度浮點(diǎn)數(shù)菠齿。




    位置插補(bǔ)器



    接下來佑吝,創(chuàng)建一個(gè)PositionInterpolator組件類型,該組件類型Rigidbody通過帶有float參數(shù)的公共方法Interpolate在兩個(gè)可配置位置之間插值可配置位置绳匀。請(qǐng)使用Vector3.LerpUnclamped以便提供的值不會(huì)受到限制芋忿,而將其留給調(diào)用者。我們必須通過其MovePosition方法更改身體的位置襟士,以便將其解釋為運(yùn)動(dòng)盗飒,否則將成為隱形傳送。


    
    
    using UnityEngine;
    public class PositionInterpolator : MonoBehaviour {
    [SerializeField] Rigidbody body = default; [SerializeField] Vector3 from = default, to = default; public void Interpolate (float t) { body.MovePosition(Vector3.LerpUnclamped(from, to, t)); }}



    通過將sider和interpolator都添加到同一平臺(tái)對(duì)象陋桂,我創(chuàng)建了一個(gè)簡(jiǎn)單的移動(dòng)平臺(tái)逆趣。內(nèi)插器方法Interpolate的動(dòng)態(tài)版本綁定到滑塊的事件,這就是為什么其值沒有字段的原因嗜历。然后宣渗,我將滑塊連接到檢測(cè)區(qū)域,以便在有物體進(jìn)入該區(qū)域時(shí)激活平臺(tái)梨州。請(qǐng)注意痕囱,插值點(diǎn)位于世界空間中。





    自動(dòng)倒車



    我們可以通過向添加一個(gè)可配置的自動(dòng)反向切換來使插值來回移動(dòng)AutomaticSlider暴匠。這需要我們跟蹤它是否被反轉(zhuǎn)鞍恢,并將FixedUpdate中的代碼加倍,必須支持雙向。同樣帮掉,當(dāng)自動(dòng)反轉(zhuǎn)激活時(shí)弦悉,我們必須跳動(dòng)而不是鉗制該值。在持續(xù)時(shí)間極短的情況下蟆炊,這可能會(huì)導(dǎo)致過沖稽莉,因此反彈后我們?nèi)匀粫?huì)鉗住。


    [SerializeField]  bool autoReverse = false;

    bool reversed;
    void FixedUpdate () { float delta = Time.deltaTime / duration; if (reversed) { value -= delta; if (value <= 0f) { if (autoReverse) { value = Mathf.Min€(1f, -value); reversed = false; } else { value = 0f; enabled = false; } } } else { value +=delta; if (value >= 1f) { if (autoReverse) { value = Mathf.Max(0f, 2f - value); reversed = true; } else { value = 1f; enabled = false; } } } onValueChanged.Invoke(value); }





    平穩(wěn)步伐



    線性插值的運(yùn)動(dòng)是剛性的涩搓,反轉(zhuǎn)時(shí)速度會(huì)突然變化污秆。通過將值的平滑變體傳遞給事件,我們可以使其加速和減速昧甘。我們通過應(yīng)用smoothstep功能給它良拼,這是3V 2 - 2V 3。使它成為可配置的選項(xiàng)疾层。



      [SerializeField]  bool autoReverse = false, smoothstep = false;
    float SmoothedValue => 3f * value * value - 2f * value * value * value; void FixedUpdate () { onValueChanged.Invoke(smoothstep ? SmoothedValue :value); }





    更多控制



    可以通過檢測(cè)區(qū)域事件禁用滑塊組件來暫停動(dòng)畫将饺,但是我們也可以控制其方向。最簡(jiǎn)單的方法是通過公共屬性提供其反轉(zhuǎn)狀態(tài)痛黎。用自動(dòng)Reversed屬性替換該reversed字段,并調(diào)整其他代碼的大小寫以使其匹配刮吧。


      //bool reversed;  public bool Reversed { get; set; }


    讓我們對(duì)自動(dòng)反轉(zhuǎn)選項(xiàng)執(zhí)行相同的操作湖饱。在這種情況下,我們必須保留序列化字段杀捻,因此添加一個(gè)顯式屬性井厌。


    public bool AutoReverse {    get => autoReverse;    set => autoReverse = value;  }



    請(qǐng)注意,方向反轉(zhuǎn)是突然的致讥,因?yàn)樗匀皇呛?jiǎn)單的插值仅仆。如果要在任何時(shí)候平穩(wěn)停止和反轉(zhuǎn),則需要?jiǎng)?chuàng)建使用加速度和速度的更復(fù)雜的邏輯垢袱。




    碰撞碰撞



    移動(dòng)風(fēng)景的危險(xiǎn)是墓拜,身體最終可能會(huì)陷入兩個(gè)接近的對(duì)撞機(jī)之間。當(dāng)對(duì)撞機(jī)之間的縫隙關(guān)閉時(shí)请契,身體要么被彈出咳榜,要么最終被推入對(duì)撞機(jī)或通過對(duì)撞機(jī)。如果碰撞表面成一定角度爽锥,則存在清晰的逃生路徑涌韩,身體將朝該方向被推動(dòng)。如果不是這樣氯夷,或者如果沒有足夠的時(shí)間逃脫臣樱,則身體最終會(huì)被壓碎,從而穿透對(duì)撞機(jī)。如果一個(gè)物體卡在兩個(gè)足夠厚的簡(jiǎn)單對(duì)撞機(jī)之間雇毫,那么它可以留在它們內(nèi)部玄捕,一旦有一條清晰的道路就會(huì)彈出。否則會(huì)掉下去嘴拢。



    如果碰撞表面成一定角度桩盲,則身體將被推到一邊,并且很有可能逃脫席吴。因此赌结,通過在表面之間留出足夠的空間或通過引入傾斜的對(duì)撞機(jī)(無論是否可見)來設(shè)計(jì)這樣的配置是一個(gè)好主意。此外孝冒,將盒子對(duì)撞機(jī)隱藏在地板上可以使它更牢固柬姚,以免物體被推過∽校或者量承,添加一個(gè)區(qū)域,在適當(dāng)?shù)臅r(shí)候觸發(fā)該區(qū)域的破壞穴店,表示它被壓碎了撕捍。





    局部插值



    世界空間中的配置可能會(huì)帶來不便,因?yàn)樗鼰o法在多個(gè)位置用于同一動(dòng)畫泣洞。因此忧风,我們通過給PositionInterpolator添加一個(gè)本地空間選項(xiàng)來包裝一下。為此球凰,我們添加了一個(gè)可選的可配置的相對(duì)于插值發(fā)生位置的Transform狮腿。通常用插值器引用對(duì)象,但這不是必需的呕诉。


    [SerializeField]  Transform relativeTo = default;
    public void Interpolate (float t) { Vector3 p; if (relativeTo) { p = Vector3.LerpUnclamped( relativeTo.TransformPoint(from), relativeTo.TransformPoint(to), t ); } else { p = Vector3.LerpUnclamped(from, to, t); } body.MovePosition(p); }



    想知道下一個(gè)教程何時(shí)發(fā)布嗎缘厢?關(guān)注微信公眾號(hào)(u3dnotes)吧!


    資源庫(Repository)

    https://bitbucket.org/catlikecodingunitytutorials/movement-10-reactive-environment/


    往期精選

    Unity3D游戲開發(fā)中100+效果的實(shí)現(xiàn)和源碼大全 - 收藏起來肯定用得著

    Shader學(xué)習(xí)應(yīng)該如何切入甩挫?

    UE4 開發(fā)從入門到入土


    聲明:發(fā)布此文是出于傳遞更多知識(shí)以供交流學(xué)習(xí)之目的贴硫。若有來源標(biāo)注錯(cuò)誤或侵犯了您的合法權(quán)益,請(qǐng)作者持權(quán)屬證明與我們聯(lián)系捶闸,我們將及時(shí)更正夜畴、刪除,謝謝删壮。

    原作者:Jasper Flick

    原文:

    https://catlikecoding.com/unity/tutorials/movement/reactive-environment/

    翻譯贪绘、編輯、整理:MarsZhou


    More:【微信公眾號(hào)】?u3dnotes

  • 最后編輯于
    ?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
    • 序言:七十年代末央碟,一起剝皮案震驚了整個(gè)濱河市税灌,隨后出現(xiàn)的幾起案子均函,更是在濱河造成了極大的恐慌,老刑警劉巖菱涤,帶你破解...
      沈念sama閱讀 212,816評(píng)論 6 492
    • 序言:濱河連續(xù)發(fā)生了三起死亡事件苞也,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡粘秆,警方通過查閱死者的電腦和手機(jī)峦筒,發(fā)現(xiàn)死者居然都...
      沈念sama閱讀 90,729評(píng)論 3 385
    • 文/潘曉璐 我一進(jìn)店門晓褪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事帆精∫衷拢” “怎么了竹观?”我有些...
      開封第一講書人閱讀 158,300評(píng)論 0 348
    • 文/不壞的土叔 我叫張陵土辩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我摘符,道長(zhǎng)贤斜,這世上最難降的妖魔是什么? 我笑而不...
      開封第一講書人閱讀 56,780評(píng)論 1 285
    • 正文 為了忘掉前任逛裤,我火速辦了婚禮瘩绒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘带族。我一直安慰自己草讶,他們只是感情好,可當(dāng)我...
      茶點(diǎn)故事閱讀 65,890評(píng)論 6 385
    • 文/花漫 我一把揭開白布炉菲。 她就那樣靜靜地躺著,像睡著了一般坤溃。 火紅的嫁衣襯著肌膚如雪拍霜。 梳的紋絲不亂的頭發(fā)上,一...
      開封第一講書人閱讀 50,084評(píng)論 1 291
    • 那天薪介,我揣著相機(jī)與錄音祠饺,去河邊找鬼。 笑死汁政,一個(gè)胖子當(dāng)著我的面吹牛道偷,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播记劈,決...
      沈念sama閱讀 39,151評(píng)論 3 410
    • 文/蒼蘭香墨 我猛地睜開眼勺鸦,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了目木?” 一聲冷哼從身側(cè)響起换途,我...
      開封第一講書人閱讀 37,912評(píng)論 0 268
    • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后军拟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體剃执,經(jīng)...
      沈念sama閱讀 44,355評(píng)論 1 303
    • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
      茶點(diǎn)故事閱讀 36,666評(píng)論 2 327
    • 正文 我和宋清朗相戀三年懈息,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肾档。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
      茶點(diǎn)故事閱讀 38,809評(píng)論 1 341
    • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辫继,死狀恐怖怒见,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情骇两,我是刑警寧澤速种,帶...
      沈念sama閱讀 34,504評(píng)論 4 334
    • 正文 年R本政府宣布,位于F島的核電站低千,受9級(jí)特大地震影響配阵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜示血,卻給世界環(huán)境...
      茶點(diǎn)故事閱讀 40,150評(píng)論 3 317
    • 文/蒙蒙 一棋傍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧难审,春花似錦瘫拣、人聲如沸。這莊子的主人今日做“春日...
      開封第一講書人閱讀 30,882評(píng)論 0 21
    • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至黔姜,卻和暖如春拢切,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秆吵。 一陣腳步聲響...
      開封第一講書人閱讀 32,121評(píng)論 1 267
    • 我被黑心中介騙來泰國(guó)打工淮椰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纳寂。 一個(gè)月前我還...
      沈念sama閱讀 46,628評(píng)論 2 362
    • 正文 我出身青樓主穗,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親毙芜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子忽媒,可洞房花燭夜當(dāng)晚...
      茶點(diǎn)故事閱讀 43,724評(píng)論 2 351