輸入緩衝與土狼時間的討論與實現
這系列將會記錄我在搭建自己的 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/
相關文章
- 帶緩衝的輸入/輸入流
- Duilib的雙緩衝實現,附帶GDI、WTL的雙緩衝實現UI
- 輸出緩衝
- C語言清空輸入緩衝區C語言
- 掌握時間與空間:深入探討Golang中的時間戳與時區轉換Golang時間戳
- PHP的輸出緩衝區PHP
- 論時間與!=EOF的絕對關聯
- PHP 輸出緩衝區應用PHP
- 直播系統程式碼,輸入時實現密碼顯示與隱藏密碼
- Java緩衝輸出位元組流BufferedOutputStreamJava
- 不到兩個月的時間 - 比特幣現金升級討論升溫比特幣
- pwntools緩衝區溢位與棧沒對齊
- Qt5雙緩衝機制與例項QT
- 討論TableLayoutPanel載入緩慢和閃爍問題解決方案
- 一個簡單的時間視窗設計與實現
- CSAPP緩衝實驗buflabAPP
- .NET 高效能緩衝佇列實現 BufferQueue佇列
- 程式分析與優化 - 9 附錄 XLA的緩衝區指派優化
- Python資料的輸入與輸出Python
- AUTOCAD——圖形的輸入與輸出
- python:檔案的輸入與輸出Python
- HTML中實現多選一且輸入框的啟用與禁用HTML
- 緩衝區溢位實驗
- TensorFlow快速入門與實戰-彭靖田-極客時間
- 巢狀滾動效果實現討論巢狀
- W君的“PMBOK與CMMI有何區別?”討論
- 緩衝管理
- 06Numpy輸入與輸出
- linux中的輸入與輸出管理(重定向輸入,輸出,管道符)Linux
- python怎樣實時輸出時間Python
- OpenGL 之 幀緩衝 使用實踐
- 簡單探討JavaScript 與 TypeScript之間的聯絡JavaScriptTypeScript
- 從瀏覽器輸入一個網址開始討論網路傳輸的工作原理瀏覽器
- 時間戳與時間字串的多時區轉換時間戳字串
- PostgreSQL 插入時間與更新時間(qbit)SQL
- C#快速入門教程(25)—— 日期與時間C#
- 《深度學習入門:基於Python的理論與實現》 Deep Learning from Scratch深度學習Python
- 如何正確匯入mapstruct,同時避免編譯時mapstruct與lombok衝突Struct編譯Lombok