用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
用Unity重現《空洞騎士》的苦痛之路(1):動作篇
用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
用Unity重現《空洞騎士》的苦痛之路(3)——地圖篇
用Unity重現《空洞騎士》的苦痛之路(4)——特效篇
本篇難度:★★★☆☆
大家好。受苦受難的蟲子們啊,我又來繼續了。
小姐姐這廂有禮了
緊接上一期的內容,這期主要講解人物控制的程式碼。
程式碼邏輯塊相對複雜,並且程式碼較多,本期將會挑選重點進行講解,程式碼不會完全貼出(太長了),所以大部分方法都不是完整的,細節方面還請下載文章末尾的工程,開啟檢視。
本期相對於上篇難度巨幅提高,如果說上篇是normal那這篇就是very hard。食用時請注意別噎著。
移動核心邏輯(難)
為了讓遊戲中玩家的移動能夠完全被開發者掌握,本工程並沒有使用Unity的物理引擎來進行移動操作,而是手動模擬相應的物理特性並進行移動。
在2D遊戲中,玩家的移動總是能夠分解成2個方向上的位移。我們移動的邏輯實現也是這樣。
首先獲取到這一幀的移動速度,然後計算出2個方向上的位移,再計算出下一個位置是否能夠進行移動。如果無法移動,就需要對下一幀該方向的移動邏輯進行修正後再實施移動。原理如下圖:
移動原理
其中關於位置修正,我們還得手動打Box射線來檢測是否碰撞,並根據結果進行相應的操作。原理如下:
位移修正原理
請注意:由於我們是先移動X軸,在移動Y軸,並沒有進行線性的移動,會在特定情況下捨棄部分位移(即位置出現偏差)。但是由於我們的移動是每一幀進行的,此處的誤差實際上可以忽略,強迫症患者可以考慮在這個方法基礎上進行修改。
程式碼實現需要獲取到當前幀的速度,並且根據速度計算出這幀玩家給個方向的位移,用於下面的計算。然後根據移動的方向,分開處理。
先處理左右方向的位移,然後使用box射線檢測,判斷下一幀是否會碰到碰撞體或者陷阱,如果是牆壁(碰撞體),還需要通過碰撞點的位置進行修正,達到滿意的移動效果。同時根據需求,更新對應的動畫狀態。程式碼如下:
- public Vector3 moveSpeed; //每一幀的移動速度
- public Vector2 boxSize; //玩家碰撞盒的大小
- public void CheckNextMove()
- {
- Vector3 moveDistance = moveSpeed * Time.deltaTime;//當前幀的移動位移
- if (moveSpeed.x!= 0)//當左右速度有值時
- {
- RaycastHit2D lRHit2D = Physics2D.BoxCast(transform.position, boxSize, 0, Vector2.right * moveSpeed.x, 5.0f, playerLayerMask);
- if (lRHit2D.collider != null)//如果當前方向上有碰撞體
- {
- float tempXVaule = (float)Math.Round(lRHit2D.point.x, 1); //取X軸方向的數值,並保留1位小數精度。防止由於精度產生鬼畜行為
- Vector3 colliderPoint = new Vector3(tempXVaule, transform.position.y); //重新構建射線的碰撞點
- float tempDistance = Vector3.Distance(colliderPoint, transform.position); //計算玩家與碰撞點的位置
- if (tempDistance > (boxSize.x * 0.5f + distance)) //如果距離大於 碰撞盒子的高度的一半+最小地面距離
- {
- transform.position += new Vector3(moveDistance.x, 0, 0); //說明此時還能進行正常移動,不需要進行修正
- }
- else//如果距離小於 根據方向進行位移修正
- {
- float tempX = 0;//新的X軸的位置
- if (moveSpeed.x> 0)
- {
- tempX = tempXVaule - boxSize.x * 0.5f - distance + 0.05f; //多加上0.05f的修正距離,防止出現由於精度問題產生的鬼畜行為
- }
- else
- {
- tempX = tempXVaule + boxSize.x * 0.5f + distance - 0.05f;
- }
- transform.position = new Vector3(tempX, transform.position.y, 0);//修改玩家的位置
- }
- }
- else
- {
- transform.position += new Vector3(moveDistance.x, 0, 0);
- }
- }
- }
同理,上下也是一樣的。只不過需要考慮受到重力的情況。在不進行上下移動的時候,加上判斷是否在地面的功能,用來決定是否新增重力。這可以通過朝地面打射線的方式進行判斷。
程式碼如下:
- public bool CheckIsGround()
- {
- RaycastHit2D hit2D = Physics2D.BoxCast(transform.position, boxSize, 0, Vector2.down,5f, playerLayerMask);
- if (hit2D.collider != null)
- {
- float tempDistance = Vector3.Distance(transform.position, hit2D.point);
- if (tempDistance > (boxSize.y * 0.5f + distance))//如果距離大於 碰撞盒子的高度的一半+最小地面距離
- {
- return false;
- }
- else
- {
- return true;
- }
- }
- else
- {
- return false;
- }
- }
左右移動
完成了上面主要移動核心邏輯、並在Update函式呼叫後,接下來的內容就要稍微簡單一些。
由於實際的移動是由別的函式來進行實現,那麼左右移動函式就只需要根據玩家的按鍵輸入,來更新玩家的速度即可。如下:
- public void LRMove()
- {
- float h = Input.GetAxis("Horizontal");
- moveSpeed.x = h * speed;//更新左右軸上的速度
- //接下來更新各種動畫狀態
- }
跳躍
在《空洞騎士》中,跳躍是根據按鍵的蓄力時長,來控制跳躍高度。如果我們需要實現這個功能,就需要在KeyDown事件中觸發跳躍,key事件中進行蓄力,KeyUp事件中停止跳躍蓄力,跳躍狀態取消,並更新動畫。邏輯圖如下:
跳躍功能邏輯圖
根據上面的邏輯圖實現的跳躍程式碼如下:
- public float jumpTime; //跳躍的最大蓄力時間
- float timeJump; //跳躍當前的蓄力時間
- public void Jump()
- {
- if (Input.GetKeyDown(KeyCode.Space))
- {
- jumpState = true; //進入跳躍狀態
- moveSpeed.y += jumpPower;//初始新增向上的力
- timeJump = 0;//蓄力時間清零
- }
- else if (Input.GetKey(KeyCode.Space) && jumpCount<=2 && jumpState)
- {
- timeJump += Time.deltaTime;//蓄力時間增加
- if (timeJump < jumpTime)
- {
- moveSpeed.y += jumpPower;//蓄力
- }
- }
- else if (Input.GetKeyUp(KeyCode.Space))
- {
- jumpState = false;//退出跳躍狀態
- timeJump = 0;//蓄力時間清零
- }
- }
二段跳實現的方法原理也是一樣,只是需要在KeyDown事件中,區分當前是二段跳還是一段跳,並分別進行蓄力操作即可。
暗影衝刺
在衝刺狀態下,玩家不受到重力,且此時不會接受按鍵輸入。於是我們需要宣告2個變數分別用來控制是否獲取按鍵輸入,以及應用重力。並在衝刺開始的時候關閉對應的開關,給玩家一個固定的移動速度,在衝刺結束後重新開啟對應的開關,速度回滾。邏輯圖如下:
暗影衝刺邏輯圖
實現的程式碼如下:
- public bool gravityEnable; //重力開關
- public bool inputEnable; //接受輸入開關 true 遊戲接受按鍵輸入 false不接受按鍵輸入
- public void SprintFunc()
- {
- if (Input.GetKeyDown(KeyCode.J) && isCanSprint)
- {
- StartCoroutine(SprintMove(sprintTime));//衝刺協程
- }
- }
- IEnumerator SprintMove(float time)
- {
- inputEnable = false;
- gravityEnable = false;//關閉按鍵輸入,以及不在應用重力
- moveSpeed.y = 0;//Y軸速度清零
- isCanSprint = false;
- if (nowDir == PlayDir.Left)
- {
- moveSpeed.x = 15*-1;
- }
- else
- {
- moveSpeed.x = 15;
- }//根據方向施加速度
- yield return new WaitForSeconds(time);//延遲time秒後執行
- inputEnable = true;
- gravityEnable = true;
- isCanSprint = true;//狀態回滾
- }
超級衝刺原理同上,但是需要考慮到需要蓄力,並且該狀態結束是由按鍵與移動進行控制的。所以我們在實現超級衝刺的時候,需要用兩種方式來結束。思路圖如下:
超級衝刺邏輯圖
程式碼就不貼出了,歡迎參考工程食用。
爬牆切換
原版遊戲裡,在空中時碰到特定牆壁會進入到爬牆的狀態。
條件是下一幀需要碰到牆壁,且距離地面有一定的高度才行。程式碼如下:
- public void EnterClimpFunc(Vector3 rayPoint) //移動檢測到下一幀碰到牆壁時呼叫
- {
- //設定碰到牆 且 從碰撞點往下 玩家碰撞盒子高度內 沒有碰撞體 就可進入碰撞狀態。
- RaycastHit2D hit2D = Physics2D.BoxCast(rayPoint, boxSize, 0, Vector2.down, boxSize.y, playerLayerMask);
- if (hit2D.collider != null)
- {
- Debug.Log("無法進入爬牆狀態 "+ hit2D.collider.name);
- }
- else
- {
- playAnimator.SetTrigger("IsClimb");//動畫切換
- isClimb = true;
- isCanSprint = true; //爬牆狀態,衝刺重置
- }
- }
有3種方式可以退出爬牆狀態:
1.玩家自己受重力下落,超出牆壁的範圍。
2.玩家按下跳躍鍵退出。
3.玩家按下反方向移動鍵退出。
這裡只提一下跳躍退出實現的原理。比較簡單,就不貼出程式碼了。在按下跳躍鍵後朝著牆壁的反方向施加一個朝上的力,就可使玩家離開碰撞體,退出爬牆狀態。圖示如下:
跳躍退出實現原理圖
攻擊互動
在《空洞騎士》中,攻擊碰到特定的物體時,會有後座力的效果,而且會重新整理玩家身上特定的技能狀態。實現邏輯就是在攻擊的時候,進行射線檢測,並根據碰撞體的標籤,以及攻擊的方向,來決定狀態的重新整理,以及是否施加後坐力的效果。邏輯圖大致如下:
攻擊判定邏輯圖
在程式碼中的實現:
- public void AttackFunc()
- {
- if (Input.GetKeyDown(KeyCode.K))//按下攻擊鍵
- {
- CheckAckInteractive((int)nowDir);
- }
- }
- public void CheckAckInteractive(int dir) //引數為攻擊的方向
- {
- float distance = 1.8f; //射線的檢測長度
- RaycastHit2D hit2D = new RaycastHit2D();
- Vector2 raySize = new Vector2(boxSize.x + 0.5f, boxSize.y); //擴大檢測X軸範圍
- switch (dir)
- {
- case 1:
- hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.left, distance, playerLayerMask);
- break;
- case 2:
- hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.right, distance, playerLayerMask);
- break;
- case 3:
- hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.up, distance, playerLayerMask);
- break;
- case 4:
- hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.down, distance, playerLayerMask);
- break;
- }
- if (hit2D.collider!=null)
- {
- if (hit2D.collider.gameObject.CompareTag("Trap")) //如果是陷阱就有後坐力
- {
- StartCoroutine(InteractiveMove(dir, 10)); //開啟協程 施加後座力效果,並重新整理狀態
- }
- }
- }
結語
完成了本期文章的內容後,我們的主角在動作方面已經完全滿足了這個專案的要求。
接下來需要完善的就是發揮自己的創意,搭建快樂地圖啦。而且在本期文章中,考慮到有的童鞋可能動畫切換這塊的想法跟我不一致,我有意刪減了動畫切換相關的程式碼。若是需要參考的話,歡迎點選文章末尾的下載連結下載後進行檢視。(PS:我使用的是2017.4.17f1版本)
工程下載連結
連結:https://pan.baidu.com/s/1wJTQmSup2EOOdGBYVmYvVw提取碼:etvn
相關連結,很(mai)重(mai)要(mai)
空洞騎士購買連結:https://store.steampowered.com/app/367520/Hollow_Knight/
有線下學習遊戲開發打算的童鞋,歡迎訪問http://levelpp.com/。
線上課程的傳送門則如下:簡明易懂的C#入門指南-網易雲課堂study.163.com
另有專業開發交(gao)流(ji)群等待大家強勢插入:869551769
作者:繁華如夢
專欄地址:https://zhuanlan.zhihu.com/p/58882234
相關文章
- 用Unity重現《空洞騎士》的苦痛之路(4)——特效篇Unity特效
- 用Unity重現《空洞騎士》的苦痛之路(1):動作篇Unity
- 用Unity重現《空洞騎士》的苦痛之路(3)——地圖篇Unity地圖
- Android 專案重構之路:實現篇Android
- Unity3d 人物的跳躍Unity3D
- 2-39. 建立 TransitionManager 控制人物場景切換
- Android 專案重構之路:介面篇Android
- 我的WebDesign之路--提高篇[2] (轉)Web
- 用Unity做半個2D戰棋小遊戲(四):加入玩家控制Unity遊戲
- Android 專案重構之路:架構篇Android架構
- 用Unity實現彈反效果Unity
- PAINGATE 苦痛系AI
- Unity控制把執行Unity
- 「Golang成長之路」併發之channel篇2Golang
- 圖說區塊鏈——————3、人物篇區塊鏈
- 歡聚——圖靈生日會人物篇圖靈
- unity小恐龍模型控制Unity模型
- 解Bug之路-dubbo應用無法重連zookeeper
- 技術筆記(7)Unity匯入人物和場景資源,出現的材質顯示問題筆記Unity
- Vue2.0 探索之路——元件複用重複響應的研究Vue元件
- Unity-2DUnity
- 在Unity中實現2D光照系統Unity
- Unity3D中實現幀同步 - Part 2Unity3D
- 開放世界關卡設計:《空洞騎士》的奧祕,2D地圖如何實現探索感?地圖
- 用Unity做個遊戲(十) - 完結篇,內容補全Unity遊戲
- RMAN 恢復之控制檔案篇(2/5)
- 用unity製作簡單的太空遊戲(2)-簡單炮臺Unity遊戲
- H2資料庫控制檯發現log4shell型別的嚴重RCE漏洞資料庫型別
- unity3D用滑鼠和射線控制物體移動Unity3D
- Seata RPC 模組的重構之路RPC
- unity 實現滑鼠控制角色移動和角色頭部的血條顯示Unity
- Unity 中層的運用Unity
- Asp.Net MVC4 系列--進階篇之路由 (2)ASP.NETMVC路由
- 喵的Unity遊戲開發之路 - 推球:遊戲中的物理Unity遊戲開發
- 如何在unity實現足夠快的2d動態光照Unity
- 喵的Unity遊戲開發之路 - 軌道攝像機Unity遊戲開發
- 2-58. 實現農作物的重複收割
- Unity URP 描邊 用RenderPassFeature實現Unity