潤稿:Ted
本文由參與GGJ 2017的遊戲開發者Ciro Continisio為大家分享他與美術設計師Jana Kilianová組成的兩人團隊,在48小時內開發出叫做Splash Clash的遊戲過程與經驗。
本文由參與GGJ 2017的遊戲開發者Ciro Continisio為大家分享他與美術設計師Jana Kilianová組成的兩人團隊,在48小時內開發出叫做Splash Clash的遊戲過程與經驗。
Global Game Jam 2017已於1月22日落幕。全球共有三萬多位遊戲開發者在700多個不同的城市參與了為期三天的遊戲創作活動,圍繞著共同的主題"Waves"製作了7000款遊戲。
Splash Clash就是其中一款優秀的遊戲。這款可以輕鬆玩的雙人對戰遊戲是由兩個像素風格角色透過跳躍製造波浪,並借助波浪將對手推出平台。下面我們從波浪效果、物理、場景設置和粒子效果這幾個方面一起來看看開發者是如何在 48 小時內將它開發出來的。
在最初,我們希望實現較為酷炫且逼真的波浪效果,所以不想用像是圓環或是粒子去擴散3D波浪物件。最終的方案是在位移著色器(Displacement Shader)中使用一張圓環貼圖,這樣就可以從平面(Plane)產生波浪,對這個圓的貼圖進行縮放,就可以實現動態的波浪。需要說明的一件事:本文分享的是如何在GGJ中快速實現這樣的效果,並非介紹實現該效果的最佳方案。可能還會有性能更好的解決方案。
初步模擬
在開始製作前,我們先在 Photoshop 中繪製了圓環貼圖並利用網路所找到的角色,例如下圖的蝙蝠俠,進行了初步模擬。
我們在Unity Manual中找到了簡單易用的位移著色器(Displacement Shader),它還支持曲面細分(Tessellation),這樣就不用擔心幾何問題了。將該著色器附加於材質後效果如下:
如上圖所示,Tessellation數值已被設為最大值,它用於控制表面被細分的次數。下面的Displacement滑動條則用於控制波浪的高度。
可行性分析
初步模擬過後,首先要確認該想法的可行性。因此,我們創建了一個白色並含有透明通道的圓環貼圖,並通過函數將其固定在不透明貼圖上。將其作為位移貼圖後的效果如下:
粗糙的開始
接下來,我擴充了函數功能,以允許更多圓環的情況。
複數靜止圓環
我們必須合成多個透明的圓環來實現表面上移動的多個波浪。如上圖所示,目前這些圓環貼圖的解析度相當高,基本都是256x256的。
由於波浪之間是相互獨立的,所以每幀必須刪除整張貼圖後再重新計算縮放的圓環。動態效果如下:
臨時彩色貼圖的隨機動態波浪效果
然而此時不得不大幅度減小貼圖大小了,因為之前的方法完全未經優化:一張512x512的凹凸貼圖(Bump)表示每個波浪都有262144的像素讀寫操作,而且256x256的波浪貼圖是隨時間縮放的,每幀都需要重寫整個貼圖。採用這種做法,屏幕上只有4個波浪時,每幀就會產生非常恐怖的1048576次像素操作。
所以我們先縮小了所有貼圖尺寸,將圓環貼圖縮至32x32,凹凸貼圖為64x64。經過少許測試後遇到了個新問題:小貼圖在縮小後再次放大會導致貼圖變形,不再是圓環。
這個問題的解決方案是暫存原始圓環貼圖,每幀都使用原始貼圖重新生成縮放後的圓環,而非一直使用同一張貼圖,因為多次縮放會導致變形。也就是說每個波浪都有一個原始貼圖的引用,但實際並未使用原始貼圖,而是僅複製其像素到重新縮放的貼圖上。
可以這種方案在平面上效果正常,但如果將材質應用到圓盤上,就會出現圓盤邊緣擠出邊界的異常效果:
圓盤左邊邊緣超出邊界導致出現鏤空
於是我們試著對著色器做些修改,不再沿著頂點法線進行擴散,改為只向上擴散。將以下程式:
v.vertex.xyz += v.normal * d;
改為:
v.vertex.xyz += float3(0,1,0)* d;
這樣就可以獲得正常的波浪效果了!
波浪沖撞完成,但波浪效果還不太明顯
實現波浪衝撞後,就要考慮彩色貼圖了。如上面動畫所示,波浪雖然有一道來自於方向光的陰影以便突出顯示,但這還遠遠不夠,還需要顯示得更為為明顯,讓玩家可以更好地用於判斷跳躍的時機。
我們起初還是使用同樣的方法,將白色的圓環貼圖放在彩色圓盤貼圖上方,與凹凸/位移貼圖的位置完全一致:
從上方的動畫中可以清楚地看到白色圓環如何顯示低解析度的貼圖,尤其是當圓環很大時。
此時我們決定調整著色器,在波浪上方就繪製白色像素。畢竟這樣每幀只需要讀取一半像素,運行效率會更快。這就需要更改surf函數中,對應計算像素顏色的程式,如下所示:
half4 c = tex2D(_MainTex, IN.uv_MainTex)* _Color *(30 * IN.worldPos.y + 1);
藉由使用IN.worldPos.y可以確保考慮到像素在世界空間的高度。像素越高,則亮度越強,也越接近於白色。效果如下:
至此,波浪特效製作完畢。
圖形問題解決後,我們下面來實現角色被波浪推開的物理效果。這個過程不太複雜,只需要每個波浪作為帶有球形碰撞器(Sphere Collider)的遊戲對象,並將碰撞設定為觸發器即可。由於貼圖是縮放的,碰撞器也會隨之縮放。
將碰撞器進行縮放以搭配位移貼圖中的波浪
一旦碰撞器與某個角色發生碰撞(OnTriggerEnter),就檢測角色的Y坐標是否低於某個特定的臨界值,該臨界值與波浪強度相關,是隨時間減小的浮點數。如果是低於這個特定的臨界值,則角色被Rigidbody上添加的力推開,這個力的大小也同樣與波浪強度成比例關係。
我們使用標籤(Tag)對碰撞器進行過濾,保證玩家1產生的波浪僅與玩家2碰撞,反之亦然。這裡稍微提一下阻力(Drag),用純物理方法對角色添加外力(AddForce)使其快速加速時,通常會使用阻力進行平衡以防止角色漂移。但添加太多阻力會讓角色在跳躍時降落緩慢,從而產生一些奇怪的漂浮物理效果。
我們在此例中實現了一個自定義阻力,藉由將 Rigidbody 速度的 x 與 z 分量乘以一個隨意因子,不改變 y 分量(重力):
rb.velocity = new Vector3(rb.velocity.x * .8f,rb.velocity.y,rb.velocity.z * .8f);
從下圖中可以看出,這個遊戲是2D與3D混合的,角色是沿X軸旋轉30度的Sprite,以配合相機旋轉,場景下方掉落的水滴與岩石也同樣旋轉。盛水的圓盤是遊戲中唯一貨真價實的3D對象,因為要用它來實現複雜而逼真的波浪。
角色都帶有 Capsule Collider (膠囊碰撞器),以及一個鎖定旋轉的 Rigidbody,保證角色只會走動不會傾斜。
其中,瀑布的粒子特效是使用一個大的圓形發射器。我還將前面的粒子與後方粒子分開,後方粒子是通過另一個粒子系統生成,放在180度的圓形模塊上。後方粒子的顏色不變,且生命週期非常短,因為它們完全不可見。
通常我們會盡可能讓所有參數(生命週期,速度,重力等等)都隨機變化,但這裡我們使用固定數值,因為希望粒子能實現像素風的感覺。同理,我們對粒子隨生命週期的顏色漸變也使用了固定模式而非混合,這樣粒子會快速改變顏色,而不是如往常一樣漸變消失。
Splash Clash的開發者Ciro Continisio向我們分享了參加Game Jam活動時的時間分配方法:將更多時間花在程式質量上而非優化,在編寫程式時採用更穩健的方法,少一些混亂程式,多一些邏輯上的類結構與信息流,以避免在最後關頭出現Bug。
Splash Clash就是其中一款優秀的遊戲。這款可以輕鬆玩的雙人對戰遊戲是由兩個像素風格角色透過跳躍製造波浪,並借助波浪將對手推出平台。下面我們從波浪效果、物理、場景設置和粒子效果這幾個方面一起來看看開發者是如何在 48 小時內將它開發出來的。
波浪效果
在最初,我們希望實現較為酷炫且逼真的波浪效果,所以不想用像是圓環或是粒子去擴散3D波浪物件。最終的方案是在位移著色器(Displacement Shader)中使用一張圓環貼圖,這樣就可以從平面(Plane)產生波浪,對這個圓的貼圖進行縮放,就可以實現動態的波浪。需要說明的一件事:本文分享的是如何在GGJ中快速實現這樣的效果,並非介紹實現該效果的最佳方案。可能還會有性能更好的解決方案。
初步模擬
在開始製作前,我們先在 Photoshop 中繪製了圓環貼圖並利用網路所找到的角色,例如下圖的蝙蝠俠,進行了初步模擬。
我們在Unity Manual中找到了簡單易用的位移著色器(Displacement Shader),它還支持曲面細分(Tessellation),這樣就不用擔心幾何問題了。將該著色器附加於材質後效果如下:
如上圖所示,Tessellation數值已被設為最大值,它用於控制表面被細分的次數。下面的Displacement滑動條則用於控制波浪的高度。
可行性分析
初步模擬過後,首先要確認該想法的可行性。因此,我們創建了一個白色並含有透明通道的圓環貼圖,並通過函數將其固定在不透明貼圖上。將其作為位移貼圖後的效果如下:
粗糙的開始
接下來,我擴充了函數功能,以允許更多圓環的情況。
複數靜止圓環
我們必須合成多個透明的圓環來實現表面上移動的多個波浪。如上圖所示,目前這些圓環貼圖的解析度相當高,基本都是256x256的。
由於波浪之間是相互獨立的,所以每幀必須刪除整張貼圖後再重新計算縮放的圓環。動態效果如下:
臨時彩色貼圖的隨機動態波浪效果
然而此時不得不大幅度減小貼圖大小了,因為之前的方法完全未經優化:一張512x512的凹凸貼圖(Bump)表示每個波浪都有262144的像素讀寫操作,而且256x256的波浪貼圖是隨時間縮放的,每幀都需要重寫整個貼圖。採用這種做法,屏幕上只有4個波浪時,每幀就會產生非常恐怖的1048576次像素操作。
所以我們先縮小了所有貼圖尺寸,將圓環貼圖縮至32x32,凹凸貼圖為64x64。經過少許測試後遇到了個新問題:小貼圖在縮小後再次放大會導致貼圖變形,不再是圓環。
這個問題的解決方案是暫存原始圓環貼圖,每幀都使用原始貼圖重新生成縮放後的圓環,而非一直使用同一張貼圖,因為多次縮放會導致變形。也就是說每個波浪都有一個原始貼圖的引用,但實際並未使用原始貼圖,而是僅複製其像素到重新縮放的貼圖上。
可以這種方案在平面上效果正常,但如果將材質應用到圓盤上,就會出現圓盤邊緣擠出邊界的異常效果:
圓盤左邊邊緣超出邊界導致出現鏤空
於是我們試著對著色器做些修改,不再沿著頂點法線進行擴散,改為只向上擴散。將以下程式:
v.vertex.xyz += v.normal * d;
改為:
v.vertex.xyz += float3(0,1,0)* d;
這樣就可以獲得正常的波浪效果了!
波浪沖撞完成,但波浪效果還不太明顯
實現波浪衝撞後,就要考慮彩色貼圖了。如上面動畫所示,波浪雖然有一道來自於方向光的陰影以便突出顯示,但這還遠遠不夠,還需要顯示得更為為明顯,讓玩家可以更好地用於判斷跳躍的時機。
我們起初還是使用同樣的方法,將白色的圓環貼圖放在彩色圓盤貼圖上方,與凹凸/位移貼圖的位置完全一致:
從上方的動畫中可以清楚地看到白色圓環如何顯示低解析度的貼圖,尤其是當圓環很大時。
此時我們決定調整著色器,在波浪上方就繪製白色像素。畢竟這樣每幀只需要讀取一半像素,運行效率會更快。這就需要更改surf函數中,對應計算像素顏色的程式,如下所示:
half4 c = tex2D(_MainTex, IN.uv_MainTex)* _Color *(30 * IN.worldPos.y + 1);
藉由使用IN.worldPos.y可以確保考慮到像素在世界空間的高度。像素越高,則亮度越強,也越接近於白色。效果如下:
至此,波浪特效製作完畢。
物理
圖形問題解決後,我們下面來實現角色被波浪推開的物理效果。這個過程不太複雜,只需要每個波浪作為帶有球形碰撞器(Sphere Collider)的遊戲對象,並將碰撞設定為觸發器即可。由於貼圖是縮放的,碰撞器也會隨之縮放。
將碰撞器進行縮放以搭配位移貼圖中的波浪
一旦碰撞器與某個角色發生碰撞(OnTriggerEnter),就檢測角色的Y坐標是否低於某個特定的臨界值,該臨界值與波浪強度相關,是隨時間減小的浮點數。如果是低於這個特定的臨界值,則角色被Rigidbody上添加的力推開,這個力的大小也同樣與波浪強度成比例關係。
我們使用標籤(Tag)對碰撞器進行過濾,保證玩家1產生的波浪僅與玩家2碰撞,反之亦然。這裡稍微提一下阻力(Drag),用純物理方法對角色添加外力(AddForce)使其快速加速時,通常會使用阻力進行平衡以防止角色漂移。但添加太多阻力會讓角色在跳躍時降落緩慢,從而產生一些奇怪的漂浮物理效果。
我們在此例中實現了一個自定義阻力,藉由將 Rigidbody 速度的 x 與 z 分量乘以一個隨意因子,不改變 y 分量(重力):
rb.velocity = new Vector3(rb.velocity.x * .8f,rb.velocity.y,rb.velocity.z * .8f);
場景設置
從下圖中可以看出,這個遊戲是2D與3D混合的,角色是沿X軸旋轉30度的Sprite,以配合相機旋轉,場景下方掉落的水滴與岩石也同樣旋轉。盛水的圓盤是遊戲中唯一貨真價實的3D對象,因為要用它來實現複雜而逼真的波浪。
角色都帶有 Capsule Collider (膠囊碰撞器),以及一個鎖定旋轉的 Rigidbody,保證角色只會走動不會傾斜。
粒子
我們為跳躍動作添加了一些粒子作為反饋,增加一種“水花四濺”的有趣體驗:其中,瀑布的粒子特效是使用一個大的圓形發射器。我還將前面的粒子與後方粒子分開,後方粒子是通過另一個粒子系統生成,放在180度的圓形模塊上。後方粒子的顏色不變,且生命週期非常短,因為它們完全不可見。
通常我們會盡可能讓所有參數(生命週期,速度,重力等等)都隨機變化,但這裡我們使用固定數值,因為希望粒子能實現像素風的感覺。同理,我們對粒子隨生命週期的顏色漸變也使用了固定模式而非混合,這樣粒子會快速改變顏色,而不是如往常一樣漸變消失。
總結
總體來說,本文涉及的一些解決方案都不是完美且未經過優化的,更像是一個為實現靈感的Demo。但是因為這是追求快速極致的 Game Jam,所以性能不那麼優秀也沒關係。Splash Clash的開發者Ciro Continisio向我們分享了參加Game Jam活動時的時間分配方法:將更多時間花在程式質量上而非優化,在編寫程式時採用更穩健的方法,少一些混亂程式,多一些邏輯上的類結構與信息流,以避免在最後關頭出現Bug。
這個好玩
回覆刪除