【整合篇】如何提升遊戲感?

目錄

序.遊戲感的介紹
1.加入細節的動畫和音效(涉及知識:音頻中間件
2.增加敵人數量且讓敵人更容易被打死(涉及知識:對象池
3.增加開火特效,加強射擊的表現力
4.讓子彈出射角度隨機變化(涉及知識:隨機數
5.增加擊中特效(涉及知識:環境系統、編輯器擴展
6.讓敵人受傷變色(涉及知識:MaterialPropertyBlock
7.敵人受傷後擊退
8.敵人死亡後屍體殘留
9.相機平滑跟隨(涉及知識:臨界阻尼弦模型、緩動函數
10.相機焦點位置根據玩家方向偏移(涉及知識:雙向正前聚焦,Procamera2D
11.相機震動(涉及知識:相機震動
12.攻擊時的後坐力
13.擊中敵人的停頓效果
14.倒退射擊
15.開槍時彈出彈殼
16.更多的子彈
17.敵人死亡時隨機爆炸
18.更多、更快的敵人
19.加強特效上的表現
20.死亡時的慢動作(涉及知識:時間系統


Jan Willem Nijman 是《廢土之王》的設計師。他在這個視頻裏Jan Willem Nijman - Vlambeer - "The art of screenshake"分享了他在 《廢土之王》用到的用於提升遊戲感(GameFeel)的手法


《廢土之王》,最早13年出現的俯視角射擊Rougulike地牢遊戲。後來被很多遊戲借鑑

什麼是遊戲感?在這本 《Game Feel: A Game Designer’s Guide to Virtual Sensation》 (中文譯名 《遊戲感:虛擬感覺的遊戲設計師指南》)定義爲:在一個模擬空間中對虛擬對象進行實時控制,過程中通過潤色來強化其交互效果。 我的理解的話就是玩家在玩遊戲時的感受,感覺。其中,強化交互效果的方法可以分爲:動畫、視覺效果、聲音效果、鏡頭效果和觸覺效果
在這裏插入圖片描述


之所以寫這篇文章,是因爲這也是涼屋遊戲的面試題之一。具體的題目是,三天做出一個射擊項目,要求包含視頻裏提到的所有設計技巧。所以我把這個視頻提到的強化遊戲感的手法總結一遍,同時梳理自己在之前工作中的開發經驗。主要是編程方面的經驗,非程序人員閱讀可能會有門檻


自己花了兩天多一點的時間做了這個

回到視頻本身。Jan首先展示了一個遊戲最原始的樣子,有角色移動、跳躍、開槍的基本功能,看上去非常的簡陋。接上來他會在不引入新玩法、新系統的情況下,通過一些細節上的改動一步一步提升遊戲感。
在這裏插入圖片描述


1.加入細節的動畫和音效

在這裏插入圖片描述
第一步,Jan 加入了人物跳躍時頭髮會動的動畫和跳躍時的音效。跳躍的頭髮擺動,感覺是非常細節的東西了,實際上玩家在玩的過程中也不容易察覺。但正是這種細節的堆積,最終決定了遊戲的品質。
編程方面,音效可以提一下。Unity本身提供的音效功能非常有限不建議使用。 遊戲開發中一般使用音頻中間件。沒有聽說過的可以看這篇文章:什麼是音頻中間件?
隨便舉個例子,玩家在攻擊時需要隨機發出語音。如果不用音頻中間件,這個功能是不是得程序員自己寫。使用音頻中間件後,一行代碼都不用寫。因爲音頻中間件在unity提供的接口是執行一個音頻事件而不是播一個音頻,這個音頻事件可以是播一個聲音,可以是播多個音頻,可以是停止某個音頻等等。音頻事件要做什麼事情完全由音頻師或策劃決定,程序員在代碼裏只需要執行這個事件即可,不需要關心這個事件是幹嘛的
目前世界級別的遊戲音頻中間件一共有三種:Criware,Wwise和Fmod。一般是前面兩個用得比較多。我個人的話用過Wwise。這篇文章記錄我用Wwsie遇到的一些問題:【音頻篇】Unity中使用wwise的常見問題及解決方案


2.增加敵人數量且讓敵人更容易被打死

在這裏插入圖片描述
接下來,Jan增加了敵人的數量,降低了敵人的血量和提升了子彈的射速,讓敵人更容易被打死。這一步是降低了玩家輸入到遊戲給玩家反饋這個循環的時間。我個人很反感一些遊戲增加遊戲難度的做法就是單純給怪堆血量堆攻擊,這會讓打怪這個過程變得很無聊。增加難度應該是用更聰明的AI,或者更有趣的遊戲機制。
編程方面要說的話就是子彈記得用對象池。 關於對象池,我總結了一篇文章【功能開發篇】使用對象池的注意事項


3.增加開火特效,加強射擊的表現力

在這裏插入圖片描述
接下來,Jan武器加上了開火的閃光特效,增加了武器射速,增加了子彈的大小
說到特效,之前做的項目裏特效類是一個很臃腫的類,但是到項目後期,花時間重構又會很影響項目進度。關於這種問題,我也寫了一篇文章:【代碼篇】如何解決函數參數過多的祖傳代碼


4.讓子彈出射角度隨機變化

在這裏插入圖片描述
這裏Jan子彈出射時有一個隨機角度變化,方向不再是直接朝右。這樣子的改動讓遊戲看起來更加有趣。
隨機數在遊戲裏也是使用非常廣的。隨機數算法有Linear congruential generator(線性同餘)Mersenne Twister(馬特賽特旋轉演算法)Xorshift等。 在圖形學裏面,上面這些算法反而用不太着,因爲太隨機了不符合自然規律。圖形學裏面被廣泛使用的是各種各樣的噪聲算法,如Perlin噪聲Value噪聲Sample噪聲等。關於噪聲算法,馮樂樂曾經分享過一篇博文:【圖形學】談談噪聲。感興趣的可以看看。


5.增加擊中特效

在這裏插入圖片描述
這裏加入了擊中環境和擊中敵人的特效。這個也是常見的需求。不過我之前遇到的需求更加複雜一點。之前做的遊戲裏的角色和環境引入了材質這個概念,爲了和渲染裏的材質做區分,叫它 ”子彈擊中材質“ 。子彈擊中材質有很多種,比如金屬、岩石、水、肉體、沙地等等。
在這裏插入圖片描述
策劃的要求是:子彈擊中不同的材質有不同的特效,並且遊戲裏的材質種類可以自由增加和刪除。 首先想到的是寫一個scriptablehuaobject,裏面存一個list去存遊戲裏有幾種材質。然後在角色和牆壁地板上掛一個類標明它是哪種材質。在這裏插入圖片描述
再在子彈上掛一個特效管理類,上面可以配置打到哪種材質會播哪種特效。
在這裏插入圖片描述
這個需求分析下來,實際上沒有什麼難的地方,我就不掛代碼了 (而且這都是公司項目源碼) 。像這種需求,它唯一麻煩的地方在於如何讓策劃配置得更舒服一點。而這種需求策劃肯定不會跟你說。比如你可以看到上圖的 BulletHitEffectController上的列表是沒有添加和刪除按鈕的,因爲我通過寫Editor讓這個列表裏有多少項元素是根據BulletHitMaterials這個scriptableobject上的配置進行自動變化的。這樣的話策劃就能看到哪些材質對應的特效是沒有配置的。
此外,還有一個更進階的要求:編輯器是否足夠美觀? 使用特殊的GUIStyle是一個不錯的辦法。關於GUIStyle的介紹,可以看這個【編輯器擴展篇】使用GUIStyle讓編輯器更好看


6.讓敵人受傷變色

在這裏插入圖片描述
我們都知道,變色這個功能改material就能實現。但是,直接改material會導致內存裏多複製一份material。所以,推薦做法是使用MaterialPropertyBlock來替換Material屬性操作。具體可以參考【UWA】使用MaterialPropertyBlock來替換Material屬性操作。我這裏做簡要介紹。MaterialPropertyBlock顧名思義,是材質屬性塊。可以從Render裏拿出它裏面的屬性,進行修改,再賦值回去。用法如下:

	_propertyBlock = new MaterialPropertyBlock();
    _renderer = GetComponent<MeshRenderer>();
    _renderer.GetPropertyBlock( _propertyBlock );
    _propertyBlock.SetColor( "_Color", Color.yellow);
    _renderer.SetPropertyBlock( _propertyBlock );

其中SetColor需要傳入Shader裏面的Properties定義的屬性名。
在這裏插入圖片描述
根據那篇UWA的博客上來看,使用MaterialPropertyBlock比直接修改Material有5倍左右的耗時差距。
關於MaterialPropertyBlock,還有以下幾點那篇博客沒有提到:

  • 同樣的Mesh使用MaterialPropertyBlock設置不同的屬性時,這些Mesh不會進行批處理。 參考MaterialPropertyBlock對性能的影響
  • 默認在Inspector上是看不到使用MaterialPropertyBlock修改後的屬性的。需要給Properties裏的屬性加上 [PerRenderData] 標籤才能看到。寫法如下:
	[PerRenderData] _MainTex ("Diffuse Texture", 2D) = "white" {}	

7.敵人受傷後擊退

在這裏插入圖片描述


8.敵人死亡後屍體殘留

在這裏插入圖片描述


9.相機平滑跟隨

在這裏插入圖片描述
關於相機平滑,可以直接使用unity提供的Vector3.SmoothDamp。網上有很多講這個函數的,但是關於裏面的原理卻沒有人提。這裏我簡單說一下。Vector3.SmoothDamp用到了Mathf.SmoothDamp【Unity的Mathf源碼】

public static float SmoothDamp(float current, float target, ref float currentVelocity, float smoothTime, [uei.DefaultValue("Mathf.Infinity")]  float maxSpeed, [uei.DefaultValue("Time.deltaTime")]  float deltaTime)
{
    // Based on Game Programming Gems 4 Chapter 1.10
    smoothTime = Mathf.Max(0.0001F, smoothTime);
    float omega = 2F / smoothTime;

    float x = omega * deltaTime;
    float exp = 1F / (1F + x + 0.48F * x * x + 0.235F * x * x * x);
    float change = current - target;
    float originalTo = target;

    // Clamp maximum speed
    float maxChange = maxSpeed * smoothTime;
    change = Mathf.Clamp(change, -maxChange, maxChange);
    target = current - change;

    float temp = (currentVelocity + omega * change) * deltaTime;
    currentVelocity = (currentVelocity - omega * temp) * exp;
    float output = target + (change + temp) * exp;

    // Prevent overshooting
    if (originalTo - current > 0.0F == output > originalTo)
    {
        output = originalTo;
        currentVelocity = (output - originalTo) / deltaTime;
    }

    return output;
}

這裏面提到了遊戲編程精粹4《用臨界阻尼實現慢入慢出的平滑》

  • 這篇文章用了帶阻力的胡克定律公式,然後對阻尼係數取一個特殊值從而讓Y軸達到平滑的效果。這樣的話這個公式畫出來的曲線就是一個S形的曲線。在這裏插入圖片描述
  • 最後公式的解是一個帶自然對數的結果:
    在這裏插入圖片描述
  • 然後對ex使用泰勒展開來進行近似計算。代碼裏面exp後面那一串就是取前三項的泰勒展開。
    在這裏插入圖片描述
  • 高數學得不好的人可能還是看不懂,別說泰勒展開,二階導數可能都看不懂。看不懂就算了。弄懂這個對工作中意義不大,遇到實際使用的情況直接複製代碼。感興趣的可以自行翻閱遊戲編程精粹4

除了臨界阻尼弦模型以外,還可以使用各種各樣的緩動函數來達到平滑的效果,像我們經常使用的Lerp就是最基礎的緩動函數DoTween 裏面自帶了大量的緩動函數,如果沒有使用DoTween插件,可以去網上找別人寫好的代碼,引入到工程。這裏提供一個我之前找的代碼:Easing.cs

在這裏插入圖片描述


10.相機焦點位置根據玩家方向偏移

在這裏插入圖片描述
可以看到相機的焦點並不是玩家位置,而是玩家方向往前偏移了一點。這是爲了能讓玩家在正前方有更開闊的視野。這種手法並不是Jan首創,在2D遊戲裏面經常能看到。在這裏插入圖片描述
專業術語叫雙向正前聚焦(dual-forward-focus)。這篇專門介紹2D攝像機理論的文章提到了這個:Scroll Back: The Theory and Practice of Cameras in Side-Scrollers。或者看這篇譯文:Scroll Back:2D 橫版遊戲攝像機運鏡原理與實踐。這篇文章非常牛逼,強烈建議大家看完。實現方面推薦使用Procamera2D插件。基本實現了上面文章提到的2D遊戲裏用到的所有攝像機機制,包括雙向正前聚焦、平臺捕捉、縮放適應、多焦點、攝像機窗格、震屏、相機邊界等等。我之前項目也是用的這個插件。基本能搞定大部分相機相關的需求,不過有一些特殊的需求需要自己手寫,比如你需要在遊戲過程中移動相機邊界,或者需要相機做指定路徑的移動等等。


11.相機震動

在這裏插入圖片描述
關於相機震動,可以看GDC上的視頻:【GDC2016】Math for Game Programmers: Juicing Your Cameras With Math。裏面提到的關於相機震動的要點總結如下:

  • 相機震動的程度和玩家受傷程度不應該是線性關係。建議是trauma2 或者trauma3 。其中trauma是一個表示玩家受傷程度,範圍在0到1的數。
  • 2D遊戲的相機震動使用位移+旋轉
  • 3D遊戲的相機震動使用旋轉。因爲使用位移有可能導致相機穿到牆壁裏,而且會讓玩家感覺遠處的震動和近處的震動程度不一樣,有違和感。
  • 在VR遊戲裏慎用相機震動。因爲會暈。
  • 使用柏林噪聲來製造隨機數。能讓相機震動更加真實,產生的隨機數更加可控。

12.攻擊時的後坐力

在這裏插入圖片描述


13.擊中敵人的停頓效果

在這裏插入圖片描述


14.倒退射擊

在這裏插入圖片描述
Jan在視頻裏演示的是,玩家在射擊時按住反方向,並不會掉頭,而是朝前開槍朝後移動。只有在鬆開射擊時,纔可以轉向。


15.開槍時彈出彈殼

在這裏插入圖片描述


16.更多的子彈

在這裏插入圖片描述
這裏將槍變成了散彈。實際遊戲裏肯定不會只有散彈這麼簡單。槍的話,用好面向對象的思想,將不同槍拆成不同類去實現就可以了。


17.敵人死亡時隨機爆炸

在這裏插入圖片描述
Jan讓敵人死亡時有33%的概率會發生爆炸。


18.更多、更快的敵人

在這裏插入圖片描述


19.加強特效上的表現

在這裏插入圖片描述
這一步加入了敵人死亡時的煙霧,把之前的爆炸效果調大,把武器射速提高。雖然玩法沒有發生什麼改變,但是看上去像那麼回事了。


20.死亡時的慢動作

在這裏插入圖片描述
最後一個是慢動作,這個涉及到遊戲中的時間系統,關於這個功能的開發,可以參考我寫的文章【功能開發篇】遊戲中的時間系統——Chronos插件源碼解析。裏面提到了時間系統如何設計,以及如何修改unity自帶組件的運行速度。


以上就是這個視頻裏提到的所有內容了,因爲不是策劃,所以如果讓我分析每個點爲什麼這麼做我也分析不上來。這篇文章更多的是去探討在編程上如何實現以及有什麼注意事項。當然單純實現這些功能很簡單,但是從一個公司項目的角度去思考就沒那麼容易了。如果只是爲了做這個小遊戲,對象池也不需要了,相機震動更不要搞什麼緩動函數,慢動作修改Time.Scale直接完事。但是正式項目肯定是不能這麼做的,要充分考慮到健壯性和可擴展性,所以纔有了這篇文章。



既然都看到這裏了,不如關注一下吧

關於作者:

  • 水曜日雞,簡稱水雞,ACG宅。曾參與索尼中國之星項目研發,具有2D聯網多人動作遊戲開發經驗。

CSDN博客:https://blog.csdn.net/j756915370
知乎專欄:https://zhuanlan.zhihu.com/c_1241442143220363264
Q羣:891809847

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章