輸入緩衝與土狼時間的討論與實現
這系列將會記錄我在搭建自己的 2D 平臺遊戲時遇到的一些問題與解決方案,核心目的均為更好的遊戲體驗與更棒的程式碼邏輯結構。所有程式碼基於 C# 與 Unity。
跳躍的手感能衡量一個 2D 平臺遊戲的好壞。——魯迅
不知道你是處理玩家跳躍的判斷條件的?反正就我而言,射線或者子物體檢測地面圖層:如果角色在地面上,則允許跳躍;反之則不允許。
但是這樣在遊玩的時候會導致一個問題:當你想要連跳時,單按跳躍鍵,你以為自己已經落到了地面,而實際上,你還在空中,從而造成了“按鍵失靈”的問題。這對於玩家的遊玩體驗有著相當大的影響。
而解決這個問題的方法,就是允許指令的預輸入,在預輸入後的一段時間內,若檢測到條件滿足,再執行操作——即“輸入緩衝”。
不過,在介紹輸入緩衝的方法前,我們先來了解一下計時器。
計時器
計時器,顧名思義,是為了計算一段時間,當計時器到達設定條件後,會執行相應的操作。
Unity 提供了一個類似的方法,
- Invoke("方法名(無參), 延遲時間")
或者
- InvokeRepeating("方法名(無參), 延遲時間, 間隔時間")
用於重複呼叫。但是限制較多,且不適用於我們的輸入緩衝:它只能做到延遲呼叫,而不能在延遲的這段時間內一滿足條件就呼叫。
另外還可以在協程中使用
- yield return new WaitForSeconds(具體秒數);
等方法實現。同樣的問題是,它也只能實現延遲呼叫。
那麼,我們到底該怎麼定義一個可用於輸入緩衝的計時器呢?以下是個人常用的一種寫法。
- <p>// 所用變數</p><p>private float timer; // 計時器</p><p>private float timer_max = 2f; // 限定時間</p><p>
- </p><p>// 初始化,一般在按下按鍵時執行,實現預輸入</p><p>timer = timer_max;</p><p>
- </p><p>// 計時過程,一般放在 Update 裡,每幀呼叫</p><p>if (timer != 0)</p><p>{</p><p> timer -= Time.deltaTime;</p><p> if (timer <= 0)</p><p> {</p><p> timer = 0;</p><p> /* 計時器到點結束執行的內容,超出限定時間,類似於延遲執行的部分 */</p><p> }</p><p> else</p><p> {</p><p> /* 計時器還在計算時的內容,在限定時間內,輸入緩衝就可以放在這 */</p><p> }</p><p>}</p>
主要思路就是利用Time.deltaTime來計算並減去時間,關於增量時間,這裡有一篇不錯的文章(https://blog.csdn.net/ChinarCSDN/article/details/82914420),就不再贅述。
那麼,接下來,利用這個計時器,實現“輸入緩衝”效果吧。
輸入緩衝
讓我們再明確下,我們想要隨時能夠輸入跳躍指令,並讓這個指令在記憶體中儲存一定時間,在該段時間內只要滿足條件(接觸地面)就執行跳躍指令。以下是兩種執行寫法(第一種為我遊戲中使用 / 第二種為在上方計時器模板上進行修改):
- <p>/* 所用變數 */</p><p>private float buffer_jump_counter = 0; // 跳躍輸入緩衝計數器</p><p>private float buffer_jump_max = 0.1f; // 跳躍輸入緩衝最大值</p><p>private bool hasJumpForce; // 此時是否擁有跳躍力了,避免重複給跳躍力,該力會在接觸地面後自動重置為 false</p><p>
- </p><p>/* 輸入指令,Update()中 */</p><p>if (Input.GetButtonDown("Jump"))</p><p>{</p><p> buffer_jump_counter = 0;</p><p>}</p><p>
- </p><p>/* 計時器與執行指令,Update()中 */</p><p>if (buffer_jump_counter < buffer_jump_max)</p><p>{</p><p> buffer_jump_counter += (1 * Time.deltaTime);</p><p> if (IsOnGround() && !hasJumpForce)</p><p> {</p><p> hasJumpForce = true;</p><p>
- </p><p> //具體施加跳躍力操作</p><p> rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);</p><p> Debug.Log("輸入緩衝,啟動一次!");</p><p> }</p><p>}</p>
下面這種我未在遊戲中測試過,不保證正確性。
- <p>/* 所用變數一致,不再贅述 */</p><p>
- </p><p>/* 輸入指令,Update()中 */</p><p>buffer_jump_counter = buffer_jump_max;</p><p>
- </p><p>/* 計時器與執行指令,Update()中 */</p><p>if (buffer_jump_counter != 0)</p><p>{</p><p> buffer_jump_counter -= Time.deltaTime;</p><p> if (buffer_jump_counter <= 0)</p><p> {</p><p> buffer_jump_counter = 0;</p><p> /* 計時器到點結束執行的內容,超出限定時間,類似於延遲執行的部分 */</p><p> }</p><p> else</p><p> {</p><p> /* 計時器還在計算時的內容,在限定時間內,輸入緩衝就可以放在這 */</p><p> if (IsOnGround() && !hasJumpForce)</p><p> {</p><p> hasJumpForce = true;</p><p>
- </p><p> //具體施加跳躍力操作</p><p> rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);</p><p> Debug.Log("輸入緩衝,啟動一次!");</p><p> }</p><p> }</p><p>}</p>
這樣,我們就實現了輸入緩衝的效果。輸入緩衝還可以用在很多的地方,如遊戲中在空中連續多次按下↓方向鍵實現砸擊地面的效果......更多的用法,就留待各位自行嘗試了。
除此之外,跳躍的輸入緩衝還有一個好兄弟,“土狼時間”。
土狼時間
土狼時間,就是讓玩家所操控的人物,能夠在離開平臺的一段時間內,仍能執行起跳操作。它的目的,也是優化操作,減少“操作失靈”的現象。那麼,我們是不是也可以用個計時器,來實現呢?可以自己先想一想。
怎麼樣,有思路了嗎?
我們只要把計時器啟動的時間改為離開地面即可,當我們離開地面,又沒有執行過跳躍,就可以在一定的時間內,執行跳躍指令。以下是兩種執行方法(同樣,第一種為我遊戲中使用 / 第二種修改自計時器模板):
- <p>/* 所用變數 */</p><p>private float buffer_coyote_counter = 0; // 跳躍土狼時間計數器</p><p>private float buffer_coyote_max = 0.1f; // 跳躍土狼時間最大值</p><p>private bool hasJumpForce; // 此時是否擁有跳躍力了,避免重複給跳躍力</p><p>
- </p><p>/* 初始化,在 Start()中 */</p><p>buffer_coyote_counter = buffer_coyote_max;</p><p>
- </p><p>/* 更新指令,該函式在 Update()中呼叫 */</p><p>void CheckForJump()</p><p>{</p><p> if (IsOnGround() && rigidbody2D_Role.velocity.y < 0.05f && rigidbody2D_Role.velocity.y > -0.05f)</p><p> {</p><p> hasJumpForce = false;</p><p> buffer_coyote_counter = 0;</p><p> }</p><p>}</p><p>
- </p><p>/* 計時器與執行指令,Update()中 */</p><p>if (buffer_coyote_counter < buffer_coyote_max)</p><p>{</p><p> if (!hasJumpForce && Input.GetButtonDown("Jump"))</p><p> {</p><p> hasJumpForce = true;</p><p> buffer_coyote_counter = buffer_coyote_max;</p><p> rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);</p><p> Debug.Log("土狼時間,啟動一次!");</p><p> }</p><p>}</p><p>
- </p><p>if (buffer_coyote_counter < buffer_coyote_max)</p><p> buffer_coyote_counter += Time.deltaTime;</p>
下面這種我未在遊戲中測試過,不保證正確性 * 2。
- <p>/* 所用變數一致,不再贅述 */</p><p>
- </p><p>/* 更新指令,該函式在 Update()中呼叫 */</p><p>void CheckForJump()</p><p>{</p><p> if (IsOnGround() && rigidbody2D_Role.velocity.y < 0.05f && rigidbody2D_Role.velocity.y > -0.05f)</p><p> {</p><p> hasJumpForce = false;</p><p> buffer_coyote_counter = buffer_coyote_max;</p><p> }</p><p>}</p><p>
- </p><p>/* 計時器與執行指令,Update()中 */</p><p>if (buffer_coyote_counter != 0)</p><p>{</p><p> buffer_coyote_counter -= Time.deltaTime;</p><p> if (buffer_coyote_counter <= 0)</p><p> {</p><p> buffer_coyote_counter = 0;</p><p> /* 計時器到點結束執行的內容,超出限定時間,類似於延遲執行的部分 */</p><p> }</p><p> else</p><p> {</p><p> /* 計時器還在計算時的內容,在限定時間內,輸入緩衝就可以放在這 */</p><p> if (!hasJumpForce && Input.GetButtonDown("Jump"))</p><p> {</p><p> hasJumpForce = true;</p><p> buffer_coyote_counter = buffer_coyote_max;</p><p> rigidbody2D_Role.AddForce(new Vector2(0f, jumpForce), ForceMode2D.Impulse);</p><p> Debug.Log("土狼時間,啟動一次!");</p><p> }</p><p> }</p><p>}</p>
怎麼樣?這樣就完美了吧。
其實關於遊戲中的跳躍,還有很多的學問,例如如何合理高效的處理跳躍各個狀態的動畫(起跳、上升、最高點、下落、落地),跳躍中額外力的施加(如馬里奧中的跳躍上升慢,下降快,並不只受到重力影響)......
其他的內容,就下次再說吧!
後記
我在學習本文相關內容時,借鑑了不少帖子、視訊,包括但不限於:
譯文|Gamemaker Studio 系列:2D 平臺遊戲的輸入緩衝 ——highway★(https://indienova.com/indie-game-development/2d-platformer-input-buffering-design/)
使用 Unity 實現動作遊戲的打擊感 —— 奧颯姆 _Awesome(https://www.bilibili.com/video/BV1fX4y1G7tv)
來源:indienova
原文:https://indienova.com/indie-game-development/input-buffering-and-coyote-time/
相關文章
- Java IO之有緩衝的文字輸入Java
- 快取與緩衝快取
- Duilib的雙緩衝實現,附帶GDI、WTL的雙緩衝實現UI
- C語言清空輸入緩衝區C語言
- 掌握時間與空間:深入探討Golang中的時間戳與時區轉換Golang時間戳
- PHP的輸出緩衝區PHP
- 《NewSQL與NoSQL的討論》SQL
- lazyload.js實現圖片緩衝載入JS
- gets()getchar()與緩衝區的問題
- [技術討論]務實與務虛
- 用apache JCS實現物件緩衝Apache物件
- Java IO之有緩衝的文字輸出Java
- 論時間與!=EOF的絕對關聯
- PHP 輸出緩衝區應用PHP
- C語言清空輸入緩衝區的N種方法對比C語言
- [全程建模]類圖與時序圖作用的對比討論時序圖
- 直播系統程式碼,輸入時實現密碼顯示與隱藏密碼
- Java緩衝輸出位元組流BufferedOutputStreamJava
- 利用TWAIN-實現與影象輸入裝置的通訊AI
- Qt5雙緩衝機制與例項QT
- pwntools緩衝區溢位與棧沒對齊
- 討論TableLayoutPanel載入緩慢和閃爍問題解決方案
- Oracle的學習路徑與方法討論Oracle
- 整理的一些SQL題,與討論SQL
- .NET 高效能緩衝佇列實現 BufferQueue佇列
- 一個簡單的時間視窗設計與實現
- [全程建模]元用例和需求與績效之間的關係討論
- C語言檔案與目錄(四)緩衝區C語言
- Golang實時GC的理論與實踐總結GolangGC
- 程式分析與優化 - 9 附錄 XLA的緩衝區指派優化
- 資料庫存取緩衝區的LRU與MRU演算法資料庫演算法
- 請問Jive的緩衝機制是怎麼實現的?
- 不到兩個月的時間 - 比特幣現金升級討論升溫比特幣
- 馴服爛程式碼之實踐、總結與討論
- [技術討論]交換程式設計實踐與延續程式設計
- 【案例討論】災難與拯救 資料安全精彩案例大討論!歡迎大家踴躍參與!
- 格式化輸出n天后的時間(java實現 )Java
- AIX 5.2上64位與32位的討論AI