用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇

遊資網發表於2019-03-28
用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
系列文章:
用Unity重現《空洞騎士》的苦痛之路(1):動作篇
用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
用Unity重現《空洞騎士》的苦痛之路(3)——地圖篇
用Unity重現《空洞騎士》的苦痛之路(4)——特效篇

本篇難度:★★★☆☆

大家好。受苦受難的蟲子們啊,我又來繼續了。

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
小姐姐這廂有禮了

緊接上一期的內容,這期主要講解人物控制的程式碼。

程式碼邏輯塊相對複雜,並且程式碼較多,本期將會挑選重點進行講解,程式碼不會完全貼出(太長了),所以大部分方法都不是完整的,細節方面還請下載文章末尾的工程,開啟檢視。

本期相對於上篇難度巨幅提高,如果說上篇是normal那這篇就是very hard。食用時請注意別噎著。

移動核心邏輯(難)

為了讓遊戲中玩家的移動能夠完全被開發者掌握,本工程並沒有使用Unity的物理引擎來進行移動操作,而是手動模擬相應的物理特性並進行移動。

在2D遊戲中,玩家的移動總是能夠分解成2個方向上的位移。我們移動的邏輯實現也是這樣。

首先獲取到這一幀的移動速度,然後計算出2個方向上的位移,再計算出下一個位置是否能夠進行移動。如果無法移動,就需要對下一幀該方向的移動邏輯進行修正後再實施移動。原理如下圖:

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
移動原理

其中關於位置修正,我們還得手動打Box射線來檢測是否碰撞,並根據結果進行相應的操作。原理如下:

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
位移修正原理

請注意:由於我們是先移動X軸,在移動Y軸,並沒有進行線性的移動,會在特定情況下捨棄部分位移(即位置出現偏差)。但是由於我們的移動是每一幀進行的,此處的誤差實際上可以忽略,強迫症患者可以考慮在這個方法基礎上進行修改。

程式碼實現需要獲取到當前幀的速度,並且根據速度計算出這幀玩家給個方向的位移,用於下面的計算。然後根據移動的方向,分開處理。

先處理左右方向的位移,然後使用box射線檢測,判斷下一幀是否會碰到碰撞體或者陷阱,如果是牆壁(碰撞體),還需要通過碰撞點的位置進行修正,達到滿意的移動效果。同時根據需求,更新對應的動畫狀態。程式碼如下:

  1. public Vector3 moveSpeed;   //每一幀的移動速度
  2.     public Vector2 boxSize;     //玩家碰撞盒的大小
  3.     public void CheckNextMove()
  4.     {
  5.         Vector3 moveDistance = moveSpeed * Time.deltaTime;//當前幀的移動位移
  6.         if (moveSpeed.x!= 0)//當左右速度有值時
  7.         {
  8.             RaycastHit2D lRHit2D = Physics2D.BoxCast(transform.position, boxSize, 0, Vector2.right * moveSpeed.x, 5.0f, playerLayerMask);
  9.             if (lRHit2D.collider != null)//如果當前方向上有碰撞體
  10.             {
  11.                 float tempXVaule = (float)Math.Round(lRHit2D.point.x, 1);                   //取X軸方向的數值,並保留1位小數精度。防止由於精度產生鬼畜行為
  12.                 Vector3 colliderPoint = new Vector3(tempXVaule, transform.position.y);      //重新構建射線的碰撞點
  13.                 float tempDistance = Vector3.Distance(colliderPoint, transform.position);   //計算玩家與碰撞點的位置
  14.                 if (tempDistance > (boxSize.x * 0.5f + distance))   //如果距離大於 碰撞盒子的高度的一半+最小地面距離
  15.                 {
  16.                     transform.position += new Vector3(moveDistance.x, 0, 0); //說明此時還能進行正常移動,不需要進行修正
  17.                 }
  18.                 else//如果距離小於  根據方向進行位移修正
  19.                 {
  20.                     float tempX = 0;//新的X軸的位置
  21.                     if (moveSpeed.x> 0)
  22.                     {
  23.                         tempX = tempXVaule - boxSize.x * 0.5f - distance + 0.05f; //多加上0.05f的修正距離,防止出現由於精度問題產生的鬼畜行為
  24.                     }
  25.                     else
  26.                     {
  27.                         tempX = tempXVaule + boxSize.x * 0.5f + distance - 0.05f;
  28.                     }
  29.                     transform.position = new Vector3(tempX, transform.position.y, 0);//修改玩家的位置
  30.                 }
  31.             }
  32.             else
  33.             {
  34.                 transform.position += new Vector3(moveDistance.x, 0, 0);
  35.             }
  36.         }
  37.     }
複製程式碼

同理,上下也是一樣的。只不過需要考慮受到重力的情況。在不進行上下移動的時候,加上判斷是否在地面的功能,用來決定是否新增重力。這可以通過朝地面打射線的方式進行判斷。

程式碼如下:

  1. public bool CheckIsGround()
  2.     {
  3.         RaycastHit2D hit2D = Physics2D.BoxCast(transform.position, boxSize, 0, Vector2.down,5f, playerLayerMask);
  4.         if (hit2D.collider != null)
  5.         {
  6.             float tempDistance = Vector3.Distance(transform.position, hit2D.point);
  7.             if (tempDistance > (boxSize.y * 0.5f + distance))//如果距離大於 碰撞盒子的高度的一半+最小地面距離
  8.             {
  9.                 return false;
  10.             }
  11.             else
  12.             {
  13.                 return true;
  14.             }
  15.         }
  16.         else
  17.         {
  18.             return false;
  19.         }
  20.     }
複製程式碼

左右移動

完成了上面主要移動核心邏輯、並在Update函式呼叫後,接下來的內容就要稍微簡單一些。

由於實際的移動是由別的函式來進行實現,那麼左右移動函式就只需要根據玩家的按鍵輸入,來更新玩家的速度即可。如下:

  1.   public void LRMove()
  2.     {
  3.         float h = Input.GetAxis("Horizontal");
  4.         moveSpeed.x = h * speed;//更新左右軸上的速度
  5.         //接下來更新各種動畫狀態
  6.     }
複製程式碼

跳躍

在《空洞騎士》中,跳躍是根據按鍵的蓄力時長,來控制跳躍高度。如果我們需要實現這個功能,就需要在KeyDown事件中觸發跳躍,key事件中進行蓄力,KeyUp事件中停止跳躍蓄力,跳躍狀態取消,並更新動畫。邏輯圖如下:

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
跳躍功能邏輯圖

根據上面的邏輯圖實現的跳躍程式碼如下:

  1. public float jumpTime;      //跳躍的最大蓄力時間
  2.     float timeJump;             //跳躍當前的蓄力時間
  3.     public void Jump()
  4.     {
  5.         if (Input.GetKeyDown(KeyCode.Space))
  6.         {
  7.             jumpState = true;  //進入跳躍狀態
  8.             moveSpeed.y += jumpPower;//初始新增向上的力
  9.             timeJump = 0;//蓄力時間清零
  10.         }
  11.         else if (Input.GetKey(KeyCode.Space) && jumpCount<=2 && jumpState)
  12.         {
  13.             timeJump += Time.deltaTime;//蓄力時間增加
  14.             if (timeJump < jumpTime)
  15.             {
  16.                 moveSpeed.y += jumpPower;//蓄力
  17.             }
  18.         }
  19.         else if (Input.GetKeyUp(KeyCode.Space))
  20.         {
  21.             jumpState = false;//退出跳躍狀態
  22.             timeJump = 0;//蓄力時間清零
  23.         }
  24.     }
複製程式碼

二段跳實現的方法原理也是一樣,只是需要在KeyDown事件中,區分當前是二段跳還是一段跳,並分別進行蓄力操作即可。

暗影衝刺

在衝刺狀態下,玩家不受到重力,且此時不會接受按鍵輸入。於是我們需要宣告2個變數分別用來控制是否獲取按鍵輸入,以及應用重力。並在衝刺開始的時候關閉對應的開關,給玩家一個固定的移動速度,在衝刺結束後重新開啟對應的開關,速度回滾。邏輯圖如下:

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
暗影衝刺邏輯圖

實現的程式碼如下:

  1. public bool gravityEnable;  //重力開關
  2.     public bool inputEnable;    //接受輸入開關  true 遊戲接受按鍵輸入  false不接受按鍵輸入
  3.     public void SprintFunc()
  4.     {
  5.         if (Input.GetKeyDown(KeyCode.J) && isCanSprint)
  6.         {
  7.             StartCoroutine(SprintMove(sprintTime));//衝刺協程
  8.         }
  9.     }
  10.     IEnumerator SprintMove(float time)
  11.     {
  12.         inputEnable = false;
  13.         gravityEnable = false;//關閉按鍵輸入,以及不在應用重力
  14.         moveSpeed.y = 0;//Y軸速度清零
  15.         isCanSprint = false;
  16.         if (nowDir == PlayDir.Left)
  17.         {
  18.             moveSpeed.x = 15*-1;
  19.         }
  20.         else
  21.         {
  22.             moveSpeed.x = 15;
  23.         }//根據方向施加速度
  24.         yield return new WaitForSeconds(time);//延遲time秒後執行
  25.         inputEnable = true;
  26.         gravityEnable = true;
  27.         isCanSprint = true;//狀態回滾
  28.     }
複製程式碼

超級衝刺原理同上,但是需要考慮到需要蓄力,並且該狀態結束是由按鍵與移動進行控制的。所以我們在實現超級衝刺的時候,需要用兩種方式來結束。思路圖如下:

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
超級衝刺邏輯圖

程式碼就不貼出了,歡迎參考工程食用。

爬牆切換

原版遊戲裡,在空中時碰到特定牆壁會進入到爬牆的狀態。

條件是下一幀需要碰到牆壁,且距離地面有一定的高度才行。程式碼如下:

  1. public void EnterClimpFunc(Vector3 rayPoint) //移動檢測到下一幀碰到牆壁時呼叫
  2.     {
  3.         //設定碰到牆 且  從碰撞點往下 玩家碰撞盒子高度內  沒有碰撞體  就可進入碰撞狀態。
  4.         RaycastHit2D hit2D = Physics2D.BoxCast(rayPoint, boxSize, 0, Vector2.down, boxSize.y, playerLayerMask);
  5.         if (hit2D.collider != null)
  6.         {
  7.             Debug.Log("無法進入爬牆狀態  "+ hit2D.collider.name);
  8.         }
  9.         else
  10.         {
  11.             playAnimator.SetTrigger("IsClimb");//動畫切換
  12.             isClimb = true;
  13.             isCanSprint = true; //爬牆狀態,衝刺重置
  14.         }
  15.     }
複製程式碼

有3種方式可以退出爬牆狀態:

1.玩家自己受重力下落,超出牆壁的範圍。

2.玩家按下跳躍鍵退出。

3.玩家按下反方向移動鍵退出。

這裡只提一下跳躍退出實現的原理。比較簡單,就不貼出程式碼了。在按下跳躍鍵後朝著牆壁的反方向施加一個朝上的力,就可使玩家離開碰撞體,退出爬牆狀態。圖示如下:

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
跳躍退出實現原理圖

攻擊互動

在《空洞騎士》中,攻擊碰到特定的物體時,會有後座力的效果,而且會重新整理玩家身上特定的技能狀態。實現邏輯就是在攻擊的時候,進行射線檢測,並根據碰撞體的標籤,以及攻擊的方向,來決定狀態的重新整理,以及是否施加後坐力的效果。邏輯圖大致如下:

用Unity重現《空洞騎士》的苦痛之路(2)——人物控制篇
攻擊判定邏輯圖

在程式碼中的實現:

  1. public void AttackFunc()
  2.     {
  3.         if (Input.GetKeyDown(KeyCode.K))//按下攻擊鍵
  4.         {
  5.             CheckAckInteractive((int)nowDir);
  6.         }
  7.     }
  8.     public void CheckAckInteractive(int dir)  //引數為攻擊的方向
  9.     {
  10.         float distance = 1.8f;          //射線的檢測長度
  11.         RaycastHit2D hit2D = new RaycastHit2D();
  12.         Vector2 raySize = new Vector2(boxSize.x + 0.5f, boxSize.y);         //擴大檢測X軸範圍
  13.         switch (dir)
  14.         {
  15.             case 1:
  16.                 hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.left, distance, playerLayerMask);
  17.                 break;
  18.             case 2:
  19.                 hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.right, distance, playerLayerMask);
  20.                 break;
  21.             case 3:
  22.                 hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.up, distance, playerLayerMask);
  23.                 break;
  24.             case 4:
  25.                 hit2D = Physics2D.BoxCast(transform.position, raySize, 0, Vector2.down, distance, playerLayerMask);
  26.                 break;
  27.         }
  28.         if (hit2D.collider!=null)
  29.         {
  30.             if (hit2D.collider.gameObject.CompareTag("Trap"))   //如果是陷阱就有後坐力
  31.             {
  32.                 StartCoroutine(InteractiveMove(dir, 10));   //開啟協程 施加後座力效果,並重新整理狀態
  33.             }
  34.         }
  35.     }
複製程式碼

結語

完成了本期文章的內容後,我們的主角在動作方面已經完全滿足了這個專案的要求。

接下來需要完善的就是發揮自己的創意,搭建快樂地圖啦。而且在本期文章中,考慮到有的童鞋可能動畫切換這塊的想法跟我不一致,我有意刪減了動畫切換相關的程式碼。若是需要參考的話,歡迎點選文章末尾的下載連結下載後進行檢視。(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

相關文章