2018年5月31日 星期四

「傘之下」開發記錄—18

發生Boss戰前,通常會段劇情動畫,營造一種Boss要來了Σ(*゚д゚ノ)ノ的感覺
所以接下來幾篇,就是要來說遊戲中的「對話」和「劇情」系統
這兩個系統,是這遊戲裡最複雜,同時也是最不滿意的東西ww
硬來的東西根本多到不能直視

嘛總之,先來看一下遊戲的對話長怎樣:
對話框是分成上下兩個,所以要操控的東西也變成兩倍……
不止啦www
為了避免各種bug,要調整的東西說不定是2.5倍
為甚麼當初要設定這種對話視窗呢?這就要問企劃
……咦,企劃就是我耶ww((自M

要寫成這個對話UI,除了要編寫怎麼控制外,還要建立一個基本的架構,來輸入對話資料:
在對話中,要更改的資料有:
.名字
.人物頭像
.說話內容
.要使用上/下對話框
.對話結束後的行動

這五個列為一個class,再在公開面版中隨時修改
然而,因為遊戲中有時會出現對話分支,因此再以一個class包住這個class "XDD
當出現分支時,選擇哪個class的資料

架構建立起來,再由別的腳本調用後,在面版中大概長這樣:
一個不太人性化的資料輸入版面w
有時亂到自己都不太懂Orz


Anyway,有架構後,就用另一個腳本來讀取資料
在腳本啟動時,會先偵察有沒有分支對話
沒有的話直接讀取對話資料,有的話則由其他腳本控制要讀第幾個分支
然後:
當按下對應按鍵時,會呼叫ClassChange
ClassChange內,則傳送「上/下對話框、名字、頭像、對話後動作」這四筆資料,給予TalkUI腳本(控制UI的腳本,會在下篇文提及)
最後再呼叫SayChange
SayChange內,會傳送對話內容給TalkUI腳本
但在傳送之前會有一個判斷式:有沒有超出對話行數
在腳本內宣告一個 int(SayNo)……SayNumber的簡寫啦不是另一意味上的SayNo  XD
當每讀取對話一次時,這個int就+1。假如SayNo大於對話行數時,就會自動跳到ClassChange,更改其餘四項資料——畢竟超出行數的意思,就是指該名角色暫時說完話
跳到ClassChange的同時SayNo變回零,同時CharacterNo+1。CharacterNo是用來偵察「有多少段對話」用的,和SayNo異曲同工
當CharacterNo超出「對話段數」時,則完結對話,呼叫ResetNumber
ResetNumber裡,除了把SayNo和CharacterNo重置外,一堆和對話(劇情)相關的參數也重置,並關閉對話視窗
因為在進行對話時要限制玩家移動
而且進行對話時可能會出現劇情……這部分也要限制
因此出現了一堆bool參數w

說了這麼多,這部分只是「傳送資料」而已
對話UI裡的更動,還需要另一個腳本協助才能達成
這部分就留在下篇吧ww

2018年5月30日 星期三

「傘之下」開發記錄—17

上一篇結尾是在講判斷Boss動作的bool
這次會貼上那段void,所以應該會更清晰一點
(沒貼在上篇,是因為在這裡的關聯性更大)

在進行判斷之前,要先講到為甚麼Boss會中止行動
當Boss受到玩家一般攻擊時,會進行防禦
在打了第一下之後,為免Boss被攻擊多下,多次觸發程式產生Bug,所以會取消攻擊判定
接著偵察被打的方位,以免防禦錯方向
防禦時會停止其他協程,然後呼叫反擊協程

反擊開始時,會設定攻擊強度(為了把玩家推開),同時更改攻擊範圍
最後則是呼叫void,繼續Boss的行動



除了一般攻擊外,Boss會被掉落的果實砸傷(或者說,Boss只會在受到果實攻擊時才會扣HP,一般攻擊時會完全防禦)
當被果實打中,會優先播放受傷動畫
然後和防禦反擊時一樣,呼叫重新行動的void


最後這個void,其實也不是甚麼厲害的東西www
就只是根據bool的開關,判斷呼叫哪個協程而已


Boss的所有行動大概就是這樣
是有些小細節的東西沒有貼出來啦,因為那些都是沒優化過硬來的東西(黑歷史)

最後再補充一下,上面幾張圖有看到個名為NextSkill的bool
那個……是沒用處的東西"XDD
因為BossAI是整個遊戲中第3個寫的腳本,是超早期的產物
雖然中途有優化過一次,但有些東西忘了刪掉_(:3」
你就知道我當初多亂來

2018年5月21日 星期一

「傘之下」開發記錄—16

小怪AI講完,當然要來個大Boss (`・ω・´)
然後因為Boss的程式太長,還是會分個兩篇就是

首先先說說Boss的攻擊模式
牠的主要攻擊為「打地板」和「高速旋轉」,這兩套動作之間的循環
兩套動作都是用協程計時,不同時間播放不同動作,組合起來才是一整套攻擊

打地板的部分:
.先是重擊地板時(攝影機同時晃動),攻擊判定範圍會增大
.果實從樹上掉下(果實自帶「可擊飛物品」腳本,以及一個控制動畫用腳本)
.接下來準備去下一組動作
(HitFloor和Rolling的bool在文末解說)


高速旋轉的部分:(Part1)
.Boss會在空間裡左右旋轉,為了避免旋轉時超出範圍,因此一開始先宣告好一個位置,用以旋轉後固定座標
.更改攻擊範圍,同時取消旋轉期間的被攻擊判定(以免被打中出Bug)
.把Boss的碰撞器更改為觸發器,重力也改為零。會這麼做,是因為用碰撞器的話,移動途中擊中玩家時會有一個「推擠」,Boss也就不能準確地移動到位置上
.把設定都弄好後,最後就讓Boss進行位移


高速旋轉的部分:(Part2)
.在旋轉途中,Boss會有一個「跌倒」動畫,這時再往前滑行一小段(這都是用時間或數值來調整,有沒有更人性化的調整方法現在還在測試)
.滑行停止後,Boss的位置固定在設置好的座標上
.恢復碰撞器及重力的數值,以及恢復被攻擊判定
.旋轉後要面對玩家,因此會有一個Flip的效果


兩套動作大致這樣,最後來講HitFloor和Rolling的bool
在設定上,Boss被一般攻擊打中時會進行防禦反擊(另一套動作),那時會停止「旋轉」和「打地板」的動作,當反擊後才重新回到這兩套動作循環裡
那這個「重新回到循環」,就是靠bool來決定
Rolling = true的時候,反擊後會切入「旋轉」的動作再次展開攻擊。反之亦然
所以當一套動作已經完成了的時候,就會開關對應的bool,讓Boss繼續行動

2018年5月20日 星期日

「傘之下」開發記錄—15

通用參數之後就是敵方的個別AI
因為地圖上的一般敵人有兩種,所以只挑其中一隻來講
結構大致上是差不多的030

調用通用參數裡的Dis,當進入攻擊範圍時就攻擊,當距離太遠時則待機
為了避免「動作」和「移動」不同步,所以「待機」和「跑步」時都設定一下速度參數

第一張圖是基本的移動,接著第二張圖算是輔助性質的void
當Hurt時:中止其餘動作,優先播放受傷動畫。呼叫StopMove。
(然後我現在才發現圖片中的StopMove多了一個 i 字"XDD)
當StopMove:敵方強制停止移動。假若HP大於0時,呼叫MoveAgain
當MoveAgain:讓敵方重新開始移動,回到第一張圖

基本上敵方AI都會是類似的模式
理論上這些行動都能分割成通用腳本,動作之間計時也能用AnimationEvent來做
但因為這是早期腳本之一,只能強硬地寫成這鬼樣子www
(沒錯雖然這篇文的發文順序較後,但卻是最早期腳本之一XD)

假如將來繼續開發下去,應該至少把「受傷」的部分分割出來
因為說不定會出現有「霸體」的敵人,這時只要把受傷腳本移除就好
不然每只敵方都從程式碼調整的話,開發時間說不定會長個兩三倍(笑

2018年5月16日 星期三

「傘之下」開發記錄—14

除了撿取「暗之花」外,打倒敵方亦會噴出花瓣
算是半延續上一篇,這次主題是敵方AI
……這兩者其實關係不大,只是想讓文章更有連貫性才這樣寫XD ((硬來

敵方AI,主要分為兩類腳本
一個是「通用數值」,另一個是「個別行動」
這篇首先會先講「共用」的部分
每只敵人都有HP,不然打不死,所以明顯地這是個共用數值
除此之外,敵方的動畫器、碰撞器、剛體等參數亦是在這裡宣告,再在其他腳本中調用
在圖片中有看到名為Dis的參數,那是用來計算敵方和玩家的距離。畢竟每只敵人都有索敵範圍,當距離超過數值時,則執行別的動作。


上面是關於敵方自身的參數,而敵方攻擊的參數則另開一個腳本
會分開寫的原因,是因為敵方的攻擊模式不同,腳本掛上的物件亦不同
 (有的掛在攻擊觸發器中,有的掛在敵方本體身上)

攻擊強度,印象中在「玩家受傷」那篇中也輕輕提到。那是影響玩家彈飛距離的參數
這個腳本中較需要說明的是OnEnable和OnDisable的部分

依現在的程式碼,敵方在攻擊時會產生一個觸發器(SetActive),當這個觸發器碰到玩家時就會構成傷害(OnTriggerEnter)
而攻擊完畢後,SetActive = false,觸發器大小變為最小

會這樣設定,是因為自己測試時,不知為何敵方的第一擊會沒有觸發判定(可能是我設定出了問題吧_(:3」)
然後就只好讓觸發器大小重置,強制把它Reset一下


通用腳本大致上就這樣
因為這次是第一次挑戰分割腳本,所以分得很不漂亮ww
很多參數都有點硬來,有些該通用的參數也沒宣告好。總之要優化這腳本是個大工程
不過說實在的,如果沒有硬寫出來,也不懂得要怎樣優化就是XD ((經驗值up

2018年5月15日 星期二

「傘之下」開發記錄—13

現時遊戲中可撿取的物品,除了劇情需要的任務物品外,就只有「暗之花」
當撿取「暗之花」時,會噴出五朵花瓣。這些花瓣就像金幣一樣,主角碰到後會收集起來

收集花瓣的效果

撿取物品的程式在上一篇講過
在那個的基礎上,再設定一個「撿取物品後會噴出花瓣」的機關就好
而要讓花瓣有「噴出來」的感覺,遊戲裡是用AddForce的方式,為每片花瓣加上作用力
加在花瓣上的作用力,往上的力度是固定的,而左右則是Random一個數值,以增加隨機感
花瓣在左右移動時,為了加強動感,亦讓它會隨著左右移動而旋轉
(其實有嘗試過用程式模擬這樣的效果,但功力不足((ry _(:3」)

花瓣噴出來後,會停留在地上,等待玩家收集
這個功能是用OnCollisionEnter來實現
在玩家碰到花瓣後,花瓣會飄向左上方(HP UI 附近的位置)
當初要實現這功能時,碰上的問題為「花瓣與UI的座標系統不同」

左邊是一般GameObject的座標,右邊是UI 的座標

為了避免玩家使用不同解析度開啟遊戲時,花瓣的位置跑掉,需要把UI的座標轉換成世界座標
理論上,座標只需要轉換一次,花瓣位置就不會太偏差
只是在花瓣飄行的過程中,玩家仍會繼續移動,所以UI位置仍會出現差異(雖然不太明顯)
因此,座標轉換的程式碼是寫在while裡面的,讓位置能持續更新
這樣會不會太吃效能,我就不太清楚了_(:3」

在花瓣飄到指定位置後,會播放粒子特效,提示玩家已收集到花瓣
與此同時,花瓣獲得數會+1。因為這數字會跟隨玩家一輩子(?),所以是宣告在共用參數腳本中


功能大致上是這樣,但老實說這腳本我也是很不滿意ww
畢竟現在每朵花瓣都要加上Rigidbody,感覺上就是個土法煉鋼的做法
如果能用腳本做出「噴發」的效果的話,在物理操控上也方便管理一點

只是嘛……這是需要花點時間研究的東西
而所謂的畢製,就是你永遠沒時間去搞研究w

2018年5月12日 星期六

「傘之下」開發記錄—12

地圖上除了有「可擊飛物品」外,也有「可撿取物品」
要講到這個,亦會提及到遊戲的關鍵物品「暗之花」

嘛不過,首先來看看撿取是甚麼東東
當玩家進入觸發範圍後,會出現一個特效提示
這個提示的出現位置,則設定在「可撿取物品」的位置上高一格(如下圖)

這是避免提示和物品的圖示重疊,影響判斷,因而這樣設計
在提示出現的同時,CanGet = true。這狀態下再按「Z」,就能觸發撿取
因為「Z」亦是攻擊按鍵,所以在進入撿取範圍內時,設定成「Z」不會攻擊,只執行撿取

撿取期間,提示特效會變成撿取特效,其位置亦改為與撿取物相同
(從原本的y = 1改成y = 0
而在0.6秒後(配合動畫),將物品的SetActive改成false
兩者配合下,就能達到撿取的效果
 

而當玩家沒撿取,就離開觸發範圍時,提示特效的z變成-10
假若沒調過攝影機數值的話,在遊戲視窗中不會顯示圖示

沒使用SetActive的原因,是因為聽說「改位置」比較省效能
所以……以後應該嘗試在地圖外設立一個物品區
當東西不需要用的時候,直接傳往該區域,而非使用SetActive
不過也不確定這樣能不能用就是www

2018年5月11日 星期五

「傘之下」開發記錄—11

開收傘是遊戲中較特殊的操作。而除了這個之外,還有一個較特殊的攻擊
 「上挑攻擊」

在前幾篇文中,在講攻擊判定時也有輕輕提到過
這個「上挑攻擊」,設定上是對魔物無效,只能打擊場景上的物品
因此,延伸出一個「可擊飛物品」的腳本

掛上這個腳本後,並增加碰撞器,物品就能被擊飛(預定之後會增加公開參數,設定被擊飛的距離)
設定上,被擊飛的物品,會對敵我雙方都構成傷害
但因為玩家在擊飛的瞬間,就有可能碰到物品而受傷。因此在觸發擊飛的同時,先取消物品與玩家的碰撞
在擊飛的同時,會更改物品的Tag和Layer,希望能減少判定上的錯誤

而在擊飛之後,當沒碰到敵人和玩家時(主要是碰到地面後),變回原來的Tag和Layer
這時才能用「上挑攻擊」繼續擊飛物品


……好了,基本上這次沒甚麼好說的了www ((好短Σ(*゚д゚ノ)ノ
因為是個尚算簡單的東西
那麼為了充一下字數,稍為提一下Physics2D好了
(這東西很難拍照,所以沒圖片抱歉……)

Physics2D,是設定不同Layer的物品,會否進行碰撞
而這文章講到的兩個Layer,SceneItem基本上只會對「地面」和「玩家的攻擊」起反應
至於FlyAway則是對「地面」、「敵方」、「玩家」起反應
其實有想過,如果FlyAway的時候,敵方剛巧在攻擊途中時,物品會被打回來
但這會牽動到更多判定問題,在有限時間內(畢製展出前) 無法完成,所以就這樣算了
假若有機會發展下去的話,應該會把這功能加進去
畢竟……這感覺很好玩d(`・∀・)b

2018年5月10日 星期四

「傘之下」開發記錄—10

延續上篇,這次來講雨傘系統


開關雨傘的判定,是寫在主角的操控腳本中的
當玩家按下左邊的Ctrl鍵時,判斷當下狀態為何,再進行相應動作
eg. 狀態:開傘中 進行動作:收傘
亦即兩者呈相反就對了

然後,當在地上進行動作時,強制停止移動
因為當初繪製動畫時,開收傘的動作是停在原地的
不停止移動的話,會看到主角腳沒在動卻會左右移動(怕

再來就是,現時遊戲設定:開收雨傘的動畫播放時,不會被攻擊到
會這樣設定,是以防動畫出現錯誤
因為在開收雨傘兩種狀態下,跑步、跳躍等動畫都是不一樣的
要實現這種判定,至少有兩種方法
先說我現在在用的:更換動畫器

當開收傘的狀態切換時,動畫器亦跟著轉換

 收傘時的動畫器

開傘時的動畫器

這樣的做法,是減少一個動畫器的複雜程度。要修改個別動畫時,能更方便地找到問題點
但限制是兩個動畫器的參數要一樣(不論參數類型還是命名)
因為是依同一行程式進行控制,稍有差錯便會出現錯誤訊息


而另一個方法是,把動畫都放在同一個動畫器中,然後多加一個布林參數「OpenUmbrella」(舉例)
然後在開傘與收傘兩組別的動畫中,都加上對應的「OpenUmbrella」,這樣也能獲得相同效果……應該說,我一開始的做法就是這樣ww
但之後因為出現各種錯誤,以及管理不方便,於是變成現在的「更換動畫器」


話題拉回來,因為要更換動畫器,如果這時被敵人攻擊到,就有更大機會出Bug
雖然有嘗試過各種方法去解決這問題,但個人功力不足,始終都有Bug
於是索性取消碰撞判定(硬來

最後再說一下更換動畫器的時機
那就是AnimationEvent出場的時候啦~

在開收傘的結尾加入腳本,就能讓兩個動畫器無縫接軌
讓我們讚嘆AnimationEvent ヽ(✿゚▽゚)ノ


話說,最近的篇幅較長,以及多加一些圖片來講述
是因為個人覺得這幾篇有點複雜,單是口頭上說說的話,除了我以外大概沒人懂吧ww
說不定現在也沒人懂就是(心虛

2018年5月9日 星期三

「傘之下」開發記錄—09

除了HP外,遊戲中還設置了「健康值」
這個「健康值」是「當下雨時,沒打開雨傘就會扣減」
所以這是三段有關聯的程式組合而成的系統(「健康值」、「下雨系統」、「雨傘系統)
但因為一次講完的話會太長,所以這次只講「健康值」和「下雨系統」

首先是下雨系統的部分
整個系統都是以協程來計算時間
當日照時間完結,便開始下雨,反之亦然

日照時間開始計算前,主要關掉下雨特效即可
而下雨時間開始前,要關掉原先的BGM,改播放下雨音效,同時開啟下雨特效

如果單單只有下雨特效,整體氛圍會略有不足
因此在兩個協程之間,加了陰天的協程
主要控制一塊陰天的畫布,來增加視覺效果

當然這個陰天畫布,也可以直接寫在「日照/下雨」的協程裡
但因為下雨前和下雨後的陰天時間,亦是氣氛渲染的一部分
獨立成一個協程的話,可以有更為彈性的變化


當現正身處下雨時間的話,名為Raining的布林值變為true
Raining = true的時候,若果沒打開雨傘,每3秒扣減一點健康值……
……好吧,其實是扣0.005的健康值www
為甚麼是這個數字,和我所使用的圖像有關係
健康值的圖示,原圖是一個圓環,但因為只需顯示1/4,所以FillAmount的上限為0.25
(測試過原圖直接用1/4,但Filled會出現問題,因此只能調小數值)
健康值原本設定上限是50,每次扣減1。在經過換算之後,就變成現在的0.005

總而言之,健康值每次會扣減相應數值
當數值跌至一定程度時,圓環顏色會更改,以提示玩家
與HP一樣,健康值是讀取共用腳本的數值,來修改圖像自身的顯示
這樣在管理及調整方面更為方便

2018年5月7日 星期一

「傘之下」開發記錄—08

受傷後自然會有HP扣減
而這遊戲中不是以血條的方式呈現HP,而是用花朵來表示(共五朵花)


當受到傷害後,花會消失
相反HP恢復後,花朵會重新出現
要實現此效果,這裡使用的方法是「開/關HP圖片的顯示」
花朵(HP)的位置是事先固定好的,HP有所更動時,才開關Image
因此程式碼需要做的,就只是那個開關


HP這東西,它的上限很多時會因一些道具、升級之類的而提升
因此一開始宣告HP的圖片時,是用GetChild的方式來宣告,以便日後增加HP時,不用重新宣告
至於MaxHP的數值,可以用transform.childCount來獲得

HP圖片宣告後之後,用兩個數值來偵察HP有沒有更動
一個是GameManager裡的HP(受到傷害時即時扣減)
而另一個是LastHP,當LastHP不等於PlayerHP時,就會呼叫HPChange來更動花朵數目
呼叫完之後,LastHP的數值亦相等於PlayerHP


在HPChange裡,宣告一個參數,計算PlayerHP - LastHP的結果
假設原先HP為5,亦即LastHP = 5。這時若受到一點傷害,PlayerHP變為4
那麼得出來的結果是-1,表示這是受到傷害
因而執行「花朵消失」的程式裡(扣HP)
反之,則執行「花朵出現」的程式(恢復HP)
而這兩套程式,結構基本上一樣,只是參數上的不同

在程式裡設立一個for loop,偵察扣減了多少HP
然後在loop裡面的a數值,其實等同PlayerHP的數值
要特意分開的原因,是為免HP的顯示出現問題
因為受到傷害時,可能不只扣減1 HP
當受到超過2或以上的傷害時,直接使用PlayerHP的話會產生錯誤

左邊是使用PlayerHP計算的結果,而右邊是使用 i 值
因此,假若每次傷害固定只有1點的話,PlayerHP會較方便
但若多點傷害時,使用loop來計算才避免顯示錯誤


最後,通常HP這類數值,都有可能一次「扣減/恢復」超過「下/上」限
因此當超過時,要把數值拉回來,不然可能出現些錯誤訊息

至於HP歸零時,因為這遊戲的死亡動畫,不是更改玩家的動作,而是獨立一套動畫
所以寫在PlayerHP的腳本裡,以方便管理

2018年5月2日 星期三

「傘之下」開發記錄—07

出拳就要被打的覺悟
所以上次講完攻擊,這次就來說受傷這東西


當玩家受傷時,因為要優先播放受傷的動畫,因此假如正在攻擊中的話,會被強制停止
然後玩家和敵方等Layer會取消碰撞,以防玩家接連受到傷害
與此同時,玩家亦短暫地不能移動
_StopMove是已經宣告好的參數,為Vector2(0, _PlayerRgbd2D.velocity.y)
意思是停止左右移動,但維持玩家本身的上下移動
沒有讓Y軸一起鎖,是想讓玩家受傷時,仍會受重力影響而下降

受傷會播放受傷動畫,而且攝影機會有一個晃動
晃動的幅度和受傷強度有關
而這個強度,每只敵方都不一樣(甚至同一只敵人的不同攻擊也不一樣)
簡單來說就是一個數值被不停調用就對了www
講到敵方AI的時候可能會補充這點吧



第一張圖的最後一句,是呼叫一個協程來製作無敵時間
所謂無敵,其實是「取消/打開玩家與敵方的碰撞」
這東西是用程式來調整,但畫面上不會有任何顯示
所以這時就要用較視覺化的呈現,令玩家知道「現在不會被攻擊到」

「無敵時間」的呈現有很多種——或者說這其實是吃美術風格的一環
 (比如可以被打中後變成蝙蝠,時間後再變回來之類的。只要程式配合到,這也不是難事XD)
 現在的做法,是用兩個Material來切換
 一個是「純白」(亦即人物原本的顏色),和一個「透明」
 透明時玩家會看不到人物,但因為是快速的切換,以人類的眼睛來說還是有辦法判斷人物的位置
而當快速切換幾次之後,恢復玩家敵方的碰撞,無敵時間因此完結
所以,這功能是美術和程式配合好的一環((廢話


然後啦……我現在這樣寫,自己也感覺有不人性化ww
要想延長無敵時間的話,還要複製那幾行程式才行,超~不方便
所以其實感覺,在協程裡用個Loop的話會方便很多
不過還是那句,這些早期的程式碼中,我甚至還駕御不了Loop這東西XD
所以嘛((遠目/