完整遊戲開發及自製簡易Buff系統-無限繼承-開發總結
完整遊戲開發及自製簡易Buff系統-無限繼承-開發總結
DEMO
github連結
(一) 概要
在第二款遊戲中,我的預計是要完成一款真的可以玩的遊戲,而非單單一個試驗品。因此,我決定採用無線波數這一個概念。遊戲中僅有一個場景,但在擊敗一波怪物後下一波便會出現,無限制的重複下去。而為了使這樣的遊戲充滿隨機性,以及不使人快速地感到無趣。我添加了隨機Buff系統,以及隨機怪物生成點等隨機要素。且透過這些Buff的組合,還能誕生出不同的玩法。
不過,可惜之處在於,並未曾查詢過相關於遊戲數據的書籍,以及上過相關遊戲設計課程,導致我在設計遊戲數值時,產生難度不一的情形。此外遊戲中的Buff也並未將機率有計畫性的分布,導致某些強力的Buff不斷出現,玩家強度超出預期。但這一點,也在下個遊戲中得到了改善。
(二) 遊戲玩法
透過上下左右進行移動,滑鼠左鍵攻擊、右鍵進行衝鋒,S鍵存檔、P鍵暫停。每次擊敗一波後,會得到選擇Buff的機會。每十關Boss會出現一次。普通的小怪僅會透過撞擊造成傷害,但Boss有不同的攻擊模式。
Boss攻擊模式,每五秒發起一次攻擊,瞬移至玩家身邊,揮刀斬擊,命中一次後,會馬上揮出第二刀,如果第二刀揮中,會再揮出第三刀。第一刀無特殊效果,第二刀命中會回復大量生命,第三刀命中會造成超強擊退。且Boss會召喚小怪,小怪不會發起攻擊,但每隻小怪,會為Boss提供強化。
(三) 腳色操作_攻擊、衝鋒(腳色池)
在這一款遊戲中,為了達成擊敗敵人的效果,我為玩家添加了更實用的攻擊方式,斬擊。透過點擊鼠標左鍵,使斬擊的動畫出現,並在斬擊的動畫上添加Istrigger,使斬擊在接觸已放置碰撞體的敵人時,會出發,從而進行扣除敵人生命的程式。
相對的,一旦敵人的攻擊方式接觸到玩家,也會觸發玩家身上的碰撞器,從而啟動玩家身上扣除生命值的程式。
至於衝鋒的技能,事實上就是在短暫的時間內,提升玩家的移動速度,但為了造成玩家的回饋心理,因此設計了殘影這一要素,使玩家感受的到,「我衝鋒了」這一反饋。至於殘影,則使用對象池來實現,透過對象池得好處是能省下很多的運行浪費,且生成的物件可以重複利用。在不足以應付當前需要的數量時,則能再動態添加。
void Awake()
{
instance = this;
//初始化對象池
FillPool();
}
public void FillPool()
{
for (int i = 0; i < shadowCount; i++)
{
var newShadow = Instantiate(shadowPrefab);
newShadow.transform.SetParent(transform);
//取消啟用,返回對象池
ReturnPool(newShadow);
}
}
public void ReturnPool(GameObject gameObject)
{
gameObject.SetActive(false);
availableObjects.Enqueue(gameObject);
}
public GameObject GetFromPool()
{
if(availableObjects.Count == 0)
{
FillPool();
}
var outShadow = availableObjects.Dequeue();
outShadow.SetActive(true);
return outShadow;
}
(圖十一) 腳色池程式碼(New RPG game\Assets\Scripts\ShadowPool.cs)。
腳色池作為殘影只是它的一個應用,用來作為射擊遊戲中的子彈,應該是較常見的用法。
(圖十二)腳色衝鋒示意圖。
(四) 敵人AI
而敵人AI也在這款遊戲中被進一步強化,雖然不具備自動尋路的能力,但也能朝著玩家的座標不斷靠近。此外,也為敵人添加了隨關卡數增加,數值隨之增加的程式。
而Boss則是這一次我嘗試著挑戰的項目,我在Boss的腳本中,設計了三段式的攻擊、瞬移的移動方式以及召喚小怪的能力。
也在Boss的頭上增加了一個倒數計時器,以方便玩家觀察Boss的攻擊模式,隨著計時器數字減少,顯示的數字顏色也會更加具有危險性。
(圖十三)Boss示意圖。
IEnumerator Ad()
{
PlayerHealth player = other.gameObject.GetComponent<PlayerHealth>();
if (attack == 0)
{
yield return new WaitForSeconds(0.1f);
Attack();
yield return new WaitForSeconds(0.4f);
if (attack == 1)
{
if(hit==1)
{
PlayerHealth.hp -= atk;
bossDamage = atk;
GameObject.Find("Player").SendMessage("BossAttack");
set = 1;
StartCoroutine(Set());
Attack();
yield return new WaitForSeconds(0.4f);
if(hit==2)
{
PlayerHealth.hp -= atk + 5;
bossDamage = atk + 5;
GameObject.Find("Player").SendMessage("BossAttack");
Boss10.hp += (atk + 5) * 35 * (ClassManager.levelNum % 10) * 6;
DamageNum damagable = Instantiate(damageCanvas, transform.position, Quaternion.identity).GetComponent<DamageNum>();
damagable.ShowUIDamage(Mathf.RoundToInt((atk + 5) * 10000), 0);
Attack();
yield return new WaitForSeconds(0.4f);
if(hit==3)
{
PlayerHealth.hp -= Boss10_Rank1.atk + 10;
bossDamage = atk + 10;
GameObject.Find("Player").SendMessage("BossAttack");
hitAfter = 1;
}
}
}
}
}
(圖十四)Boss攻擊模式程式碼
(New RPG game\Assets\Scripts\Boss10_Rank1.cs)。
(五) UI介面
遊戲介面同樣有暫停,除此之外,更添加了buff的顯示區塊,關卡的提示等。而玩家身上的血條UI,則是透過三張不同顏色的圖片,設置成填充模式以達成。至於扣血效果的UI則製成了預製體,需要時調用。最後則是分數計算的顯示,跟上一款遊戲的收集櫻桃是類似的。
(圖十五、十六、十七)UI介面。
此外,也於遊戲中製作了預製體的扣血效果,並於怪物或玩家受到傷害時,生成於玩家或是怪物的身旁。而Boss身上則有額外的綠色效果,作為回血的數值。
(圖十八)回復以及扣血效果。
(六) 程式碼_無限波數、Buff系統、存檔系統
為了提升遊戲的可玩性,以及使遊戲的紀錄可被儲存,我上網查詢有關於這方面的教學影片以及文章。三者之中,最容易達成的是無限波數的部分,透過程式碼中的變數調控,以檢測場上敵人的數目來決定是否進入下一波。
一旦進入下一波,再透過隨機生成的方式,隨機生成敵人到場上,每十關出現一次Boss。
private void Update()
{
if (enemy == 0) //召喚怪物
{
enemyCount = (ClassManager.levelNum-1) * 1 % 5;
if(enemyCount==0)
{
Appear();
}
for (int i = 0; i < enemyCount; i++)
Appear();
}
enemy = 1;
}
Record();
}
void Appear() //隨機出現位置
{
float x = Random.Range(0, 10);
float y = Random.Range(0, 10);
if ((ClassManager.levelNum % 10) != 1)
{
point.transform.position = new Vector2(point.transform.position.x + x, point.transform.position.y + y);
Instantiate(enemyBat, point.transform.position, enemyBat.transform.rotation);
point.transform.position = new Vector2(point.transform.position.x - x, point.transform.position.y - y);
Instantiate(enemyBeast, point.transform.position, enemyBeast.transform.rotation);
nowEnemyCount += 2;
}else if((ClassManager.levelNum%10)==1)
{
point.transform.position = new Vector2(point.transform.position.x - x, point.transform.position.y - y);
Instantiate(boss10, point.transform.position, boss10.transform.rotation);
nowEnemyCount += 1;
}
}
void Record() //重新激活
{
if (nowEnemyCount == 0 && run == 0)
{
if(score==1)
{
ScoreInformation.scorePower += timeScore * 10 * ((ClassManager.levelNum / 2) + 1);
timeScore = 50;
}
GameObject.Find("GameStartCanvas").SendMessage("Run");
run = 1;
score = 1;
}
}
(圖十九)敵人生成及波數程式碼
(New RPG game\Assets\Scripts\EnemyAppearManager.cs)。
至於存檔機制則是因為網路上有種類繁多的教程,因此在實作過程中,沒有遇到特別多的困難。最後我選擇PlayerPrefs作為存檔的方式,但在下一款遊戲中,我將PlayerPrefs、序列化:二進制、序列化:JSON、序列化:XML都學習過了,後邊會提到。
maxHp = PlayerHealth.maxNowHp;
PlayerPrefs.SetFloat("maxHp", maxHp);
maxHp = PlayerPrefs.GetFloat("maxHp");
(圖二十)PlayerPrefs存檔方式(New RPG game\Assets\Scripts\Load01.cs)。
最後的Buff系統則是困擾了我數天,其原因在於網路上並沒有相關的教程。因此,我開始構思一個能隨機產生Buff並給予玩家選擇的機制。
在我看來,Buff系統的核心有三個,資料庫、管道、生成。
資料庫中儲存有關於這個Buff的資料,玩家在選擇時,資料便會出現,提供玩家有關於這個Buff的能力。管道則是將程式裡的Buff系統實際顯示在遊戲屏幕上的方式,這裡我使用了UI以及隨機取數來完成,隨機生成三個可供選擇的Buff。最後的生成,則是使效果生成,只需要在程式碼中修改相關的變數即可。
Buff系統本體:
(New RPG game\Assets\Scripts\Buff.cs)。
Buff系統資料庫:
(New RPG game\Assets\Scripts\BuffData.cs)。
在實作Buff機制時,遇到了十分大的挫折,光是Buff系統的構思,便出錯了數次,導致出現奇怪的結果。但更多的是沒有任何反應,時間也一分一秒的在減少,那時候每天都是早上8點起床,坐在電腦前實作到晚上12點。
雖然實際上的程式碼不多,似乎不需要那麼久的時間,但浪費最多時間的是重覆的試驗。
除此之外,也不斷查詢是否有相關的資料能幫助我完成這項任務,儘管最後是靠著靈光一閃解決了這個問題,但如果沒有那些天累積的知識,這個點子也不會出現。
也是在這次的Buff系統過後,我開始真正掌握到編寫遊戲程式的感覺,以及真正開發著一款遊戲。
(七) 子彈功能_追蹤彈
這款遊戲最後要提到的就是追蹤彈的功能。在實作遊戲時,我常會到各個影片網站尋找對開發有幫助的影片,也是因此,意外的發現了有關於子彈追蹤的影片。實際上,如果要做出簡單的追蹤子彈效果,使用Trail就能完成。
但僅僅是子彈朝著敵人直線射擊過去,就少了一點味道,原先是不打算製作的。只是,那部影片與子彈軌跡有關,因此,在看到那個影片後,我才決定製作這一功能。
不過,這項功能的開發只算是作為研究的目的,並沒有將其實裝在遊戲中的想法,因此,實際的遊戲中需要啟動隱藏的測試按鈕,才能使用。
之所以不實裝的原因之一,是因為Buff系統的協調出了點問題,調適需要一段時間。其次,則是我當時對於這個功能的想法並不多,所以沒有下手大改這款遊戲,畢竟,本來這款遊戲的目的是設計為一款近戰遊戲。
以下是開啟射擊模式的方法:進入暫停介面,需要將鼠標移到黑塊的相應位置處,黑塊才會顯示。
(圖二十一)射擊模式開啟方法。
(圖二十二)射擊模式示意圖。
在這個系統中,可以透過調整角度的係數,從而實現不同的子彈軌跡,以下是另一種不同的軌跡示範。
(圖二十三)類似閃電的子彈軌跡示意圖。
public void Chase()
{
Vector3 direction = target.transform.position - this.transform.position; //朝向目標
float ang = 360 - Mathf.Atan2(direction.x, direction.y) * Mathf.Rad2Deg; //方向向量轉角度
transform.eulerAngles = new Vector3(0, 0, ang); //角度設為對應角度
this.transform.rotation = this.transform.rotation * Quaternion.Euler(0, 0, rotationAngle);
transform.Translate(Vector3.up * bulletSpeed * Time.deltaTime);
}
(圖二十四)實現子彈軌跡的程式碼
(New RPG game\Assets\Scripts\NormalBullet.cs)。
透過在這個程式碼中調整係數,便能做到不同的子彈軌跡效果。雖然這個功能在這款遊戲裡並沒有正式加入,但也多虧這個功能的啟發,讓我有了製作下一款遊戲的念頭,製作一款射擊類遊戲。