項目來源於:https://github.com/meta-42/UnityGameplayTutorials
關於源項目
這個項目是在b站up主“皮皮關做游戲”發布的視頻【游戲制作教程】30分鍾制作一款游戲 (6)【Unity3D】_嗶哩嗶哩_bilibili中由up主本人制作,原作者將項目源碼上傳至Github便於其他人學習。本篇博客將基於這個項目,對其進行閱讀分析,找出項目中的缺陷並對其加以簡單改進。
一.源項目簡介
源項目是基於Unity3D制作的一款簡單的橫板3D過關游戲,這類游戲中最出名的就是由任天堂制作出品的“超級馬里奧兄弟”,源項目與其基本類似,玩家需操控角色小人在場景障礙中進行跳躍行走,躲避NPC的攻擊並跳踩攻擊NPC,最終到達終點則視為過關。以下動圖為運行時截圖。
二.源項目功能及相應代碼分析
在運行過這個項目並閱讀項目源碼后,我們可以大致看出這個項目具有的幾個功能:1)角色的移動與跳躍 2)NPC的隨機移動 3)角色與NPC的攻擊 4)攝像機對角色的實時跟隨。接下來將對於這幾個功能進行具體的分析。
1)角色與NPC的移動與跳躍
以下是控制角色移動和跳躍的代碼:
public void Jump() { if (cc.isGrounded) { pendingVelocity.y = jumpPower; } } public void Move(float inputX) { pendingVelocity.x = inputX * runSpeed; }
這部分代碼較為簡單,主要是通過判斷角色是否在地面上來決定跳躍,然后通過提前設置好的跳躍能力值改變角色的垂直方向位置。同樣的道理,根據設置好的移動速度更改水平方向上的位置來實現角色橫向移動。
2)NPC的隨機移動
在游戲中,NPC會在場景中隨機移動跳躍,對角色發起攻擊。NPC的移動方向和是否跳躍都是由生成隨機數決定的,以下是代碼:
void Update () { //每隔一秒檢查一次AI狀態,確認接下來的行為 if (Time.time > lastCheckStateTime + 2) { lastCheckStateTime = Time.time; simulateInputX = Random.Range(-1f, 1f); simulateJump = Random.Range(0, 2) == 1 ? true : false; } //實際執行行為 MoveControl(simulateInputX); JumpControl(simulateJump); } void MoveControl(float inputX) { character.Move(inputX); if (inputX != 0) { var dir = Vector3.right * inputX; character.Rotate(dir, 10); } } void JumpControl(bool jump) { if (jump) { character.Jump(); simulateJump = false; } }
從代碼中可以看出,如果玩家控制角色發生移動,NPC的移動函數將隨機生成一個數字,並根據這個隨機生成的數字決定NPC的下一步行動。
3)角色與NPC的攻擊
由於角色和NPC繼承於同一個父類,所以兩者的攻擊和計算傷害函數相同,代碼如下:
public void AttackCheck() { var dist = cc.height / 2; //向下射線檢測 RaycastHit hit; if (Physics.Raycast(transform.position, Vector3.down, out hit, dist + 0.05f)) { if (hit.transform.GetComponent<Character>() && hit.transform != transform) { hit.transform.GetComponent<Character>().TakeDamage(this, damage); } } } public void Death() { var fx = Instantiate(deathFX, transform.position, Quaternion.Euler(Vector3.zero)); Destroy(fx, 2); Destroy(gameObject); } public void TakeDamage(Character inflicter, int damage) { inflicter.Jump(); health -= damage; if(health <= 0) { Death(); } }
從代碼中我們可以看出,攻擊的主要方式是以人物為中心向垂直地面向下的方向發出射線檢測,因為角色和NPC都繼承於同一父類,所以如果檢測到碰撞到的對象帶有父類的腳本,則碰撞到的對象將會調用承受攻擊函數,相應計算血量值的減少。如果血量值小於等於0,則調用死亡函數將對象銷毀,並觸發死亡動畫。
4)攝像機對角色的實時跟隨
在橫板游戲中,最具特色的就是固定角度只會橫向移動的攝像機,為了做到攝像機橫向移動並實時跟隨角色,將角色的實時位置傳給攝像機的位置函數,根據玩家的位置實時調整攝像機的位置,實現攝像機對角色的跟隨效果,代碼如下:
void LateUpdate() { if (!target) return; transform.position = target.position; //Z軸間隔距離 transform.position -= Vector3.forward * distance; transform.position = new Vector3(transform.position.x, hight, transform.position.z); }
三.對項目的簡單修改補充
1)增加關卡內容
在多次運行項目並分析其代碼后,我發現項目並不完整,還有很多可以改進的地方。首先,作為一個游戲,現有的關卡顯然太過短和簡單,只有幾個障礙,如下圖:
因此,改進的第一步即為增加關卡場景的長度和難度,為保持內容風格一致,復制原場景中的素材進行拼貼增加。修改后效果如下圖:
2)增加游戲玩法
第二,在橫板游戲中,僅有單純的跳躍障礙未免過於缺少游戲性,因此在項目中添加橫板過關游戲的經典玩法,吃金幣。首先在Unity官方商城中找到金幣的素材,將其添加入項目之中。隨后將模型放置到關卡中,並添加腳本使金幣能夠在原地旋轉。效果如下圖:
接下來需要為金幣和角色設置碰撞器,如果人物碰撞到金幣,則金幣將會消失。同時,還需要在屏幕上設置顯示一個計數器,記錄已經獲得金幣的數量。具體代碼如下:
void OnTriggerEnter(Collider collider) //檢測接觸 { if (collider.tag == "Coin") { score++; text.text = score.ToString(); } Destroy(collider.gameObject); }
效果如下:
3)增加結束判斷
為了讓這個項目變成一個完整的游戲,還需要為它設置一個結束判定,也就是游戲的最終目的——通關。由於游戲的主要玩法是跳躍吃金幣,於是將游戲的結束判定設置為收集完場景中的所有45個金幣。一旦玩家在關卡盡頭收集完全部金幣,屏幕中間就會顯示勝利的文字。於是對金幣的計數代碼進行修改,每計數一次就判斷數量是否已經到達45,具體代碼如下:
void OnTriggerEnter(Collider collider) //檢測接觸 { if (collider.tag == "Coin") { score++; text.text = score.ToString(); if (score == 45) //判斷數量是否已經達到全部收集 { winText.SetActive(true); //顯示勝利文字
} Destroy(collider.gameObject); } }
具體效果如下:
4)其他簡單添加
除了上述這些功能玩法上的添加,還做了一些簡單的小添加讓這個游戲更加完整,例如設置了運行時的背景音樂,玩家NPC攻擊時的特殊音效,死亡時的特殊音效等。
四.總結
通過這次的項目修改的過程,我產生了幾點感悟:
1)注釋非常重要。在閱讀他人代碼時,注釋能幫我快速的了解代碼的作用,極大的提高了我閱讀理解代碼的效率。少部分缺少注釋的地方則消耗了我大量時間查找理解。
2)在遇到問題時百度比自己死磕更能解決問題。有時項目出現bug的時候,與其自己嘗試修改,不如在網上搜索一下他人的經驗和方法,也許馬上就能有思路。
3)Debug的過程更能加深對代碼的理解。比起寫代碼,debug的時候更能深入理解代碼的作用, 學習到函數的具體使用方式。
以上內容均為本人個人理解,如有問題歡迎批評指正,也歡迎留言交流學習。