Flash務實主義(三)——最短路徑原則(下)

透過現象看本質

首先是個轉場特效問題。

其實我早想到做法了,只是當時還沒實踐。增加混合(BlendMode.ADD)只要關係到光效,泛用性就很高,疊加着畫上去看起來應該就是這個效果。

但是我還是先到羣裏問了下,然後一幫人就跟我說徑向模糊。還有一幫人說以前搞過,拿我的原圖上了個徑向模糊發給我。但是徑向模糊多慢用過PS都知道,怎麼也不可能用在動畫效果裏的。這個東西的確有點像徑向模糊,但是像什麼就做什麼,從表面着手,思考方式就有點單純了。

實際上做法再簡單不過,調整Matrix縮放和旋轉圖形,一點點放大,然後用增加混合模式將原圖疊加着畫上去即可。

var m:Matrix = new Matrix(); 
m.translate(-bmd.width / 2,-bmd.height / 2); 
m.scale(scale,scale); 
m.rotate(r); 
m.translate(bmd.width / 2,bmd.height / 2); 
screen.bitmapData.draw(bmd,m,new ColorTransform(1,1,1,0.2),BlendMode.ADD); 

增加混合是個很有趣的東西,可以衍生出很多東西,這個以後再說。

短時效果,近似方式可以更大膽

某天看到了個很牛的玩意,一個哥們做了個很酷的模擬水滴從牆面上滴下來的效果,公佈了詳細的思路與做法,涉及不少的數學知識(http://jamesli.cn/blog/?p=631 )。我們在這隻就他這個效果一個很有趣的細節做討論——兩個水滴接近時會自動融合成一個。

水滴本身是一個多個節點的曲線,那麼這個融合過程該怎麼實現呢?這時候一個沒看作者代碼的人就說話了,而且還畫了圖。總之,他的辦法就是當兩個水滴 相交時,先檢測出相交面積,然後找到重疊部分的曲線的點,將這部分點刪除,再將剩下的點平滑連接到一起,這樣兩個水滴就合二爲一了。好吧,至少這個沒有要 求做曲線相交,但剔除點之後重連接還是要有一定數學知識,到底哪兩個點連接?連接曲線的曲率是多少,怎麼纔算平滑?連接成一體的新的水滴怎麼才能恢復類似 圓的形狀?

而作者的代碼很簡單。他的做法僅僅是讓較小的水滴快速移動向大的水滴並漸隱,然後把兩個水滴的面積相加求出新的半徑,小水滴刪除,大水滴直接變大。 這個和真實融合的情況相差甚遠,但是由於融合過程很快(現實中確實也很快),如果你不使用變速齒輪,自己也沒有子彈時間似的超級視覺,根本看不出具體融合 步驟,也就看不出破綻。

都說圖形編程數學要好,這的確不錯。但實際上除了一些特別噁心的需求,甚至都不需要動用高數的知識。而且如果需要用到高數知識,往往也是光高數知識搞不定的。如果一個問題覺得非得用高中程度之上的知識才能解決的話,有可能就是鑽進了牛角尖。近似方案,足夠了。

算法問題並非都要全部通過公式解決

這是一個實際問題。我曾經自己寫了一個翻頁效果,並用在了項目上。翻頁實際上很難寫,雖然都是解析幾何的知識,但要判斷多種情況,各種翻頁方式的繪製方法是不一樣的。所以最後我只實現了從左上向右下的翻頁,當時足矣。

但是後來美術提要求了,他希望做個從右上向左下翻的動畫(這是一般翻書的習慣),但因爲這個是用解析幾何計算出來的,在不同方向上,公式是不同的。而這段代碼裏全是已經移項後的方程,根本無法還原,隔得時間也太長,換個方向差不多就要重寫。我頭痛了。

後來我在那無聊,把整個繪製容器翻來翻去,設置scaleX,scaleY,然後看到,翻過來以後,雖然鼠標,圖像全都倒置了,翻頁本身的效果倒是 挺正常的,只是翻轉了。垂直翻轉後,由左上向右下就成了由左下向右上,水平翻轉後,則成了從右上向左下。好,我要的效果出來了。

鼠標和貼圖不對?改下就好了,鼠標很好弄,貼圖嘛,繪製的時候先翻轉好就成。

終於不用寫四次同樣的算法了。雖然其他人寫的翻頁可能能用一套公式兼容多個方向的情況,但我的數學水平沒那麼好。但就算這樣不也做出來了?條條大路通羅馬,不修改繪製方向,而是改變整個容器的方向,雖然不是標準答案,卻是很理想的應急方案。在這裏,它幾乎可以永久使用。

固定動態內容可轉化爲動畫

大家小時候都玩過那種投色子在格子上前進的紙上游戲吧。我以前挺熱衷的,還常常自己畫棋盤,各種大小格子跳轉。如果棋盤都是很整齊的直線和折角的話,那自然好說,但如果棋盤本身的線路就是扭來扭去的,或者說本來就是美術亂畫的呢?

首先想到的是做錨點,棋盤就當做背景圖了,旗子都是在這些看不到的點上移動。這裏直線移動還是好做的,但是曲線……除非你打算用直線近似代替,否則又要搞什麼貝爾法……

但如果你的這部分只是一個小遊戲,不關心擴展的話,有一個只有FLASH才能做得到的方案,對於AS2時代的人可能會比較親切,AS3時代進來的人可能根本就見過這種搞法。

咱把棋子移動整個做成一個動畫吧。

有分支就跳轉不同幀繼續播放,棋子走的時候就gotoAndPlay(),移動僅僅是控制播放這個動畫,這樣什麼轉彎全都是動畫,做些花哨的動作比如騰躍也都行了。這個的缺點就是隻能做固定的行走路線,如果人物可以隨時轉向就有點困難(也不是不能做)。

小遊戲有小遊戲的搞法,FLASH也正是因爲可以支持這些小遊戲的搞法才能發展到現在。雖然是老掉牙的東西了,有些時候還是可以用用的。老的東西並不會被完全取代,畢竟它還是有自己的便利之處的。既然用了就能豁然開朗,那麼它就是這時候的,最佳選擇。

代碼級簡化

說得挺多了,最後再介紹一些寫法上的簡化方法吧:

  • 正弦震動

實現一個元件的震動,物理方式模擬實現是最傻的,用Tween模擬多次緩動一樣也好不到哪去。而震動指的都是正弦震動,所以我們用Math.sin()處理y軸就可以了。

你甚至不需要定義一個遞增變量來處理時間,只需要在開始直接t = getTimer()來記錄初始時間,然後像這樣y = Math.sin((getTimer() - t) / T * 2 * Math.PI) * R(getTimer() - t得出的是經過的時間,T是震動週期,然後乘以2π,就是sin函數需要的參數,而再乘以振幅R即可,這都是中學知識)

停止用setTimeout執行一個函數即可,振幅一樣可以用getTimer() - t作爲變量遞減。

九宮格方向

一般人物動畫用的8方向序列幀,需要根據鼠標指示的方向來顯示對應方向的循環序列,平常的做法就是寫上一組九個的IF語句,分別判斷九種情 況並設置九種序列幀。這沒有問題,但實際上有更簡單的做法。我們可以認爲x軸方向有3種狀態(左,中,右),y軸方向有3種狀態(上,中,下),而這些狀 態相互組合形成了結果的9種狀態。如果這三種狀態分別以數字0,1,2表示的話,可以用公式y*3+x直接得出一個狀態值(y是縱向的狀態,x是橫向的狀 態),而這個結果則是這樣的:

0(左上) 1(上) 2(右上)

3(左) 4(中) 5(右)

6(左下) 7(下) 8(右下)

這是一個9宮格,拉成一行就是一個數組,可以將各個方向的序列幀存在這個數組中,然後判斷一次x的狀態值,一次y的狀態值,然後直接用arr[y*3+x]就能取出對應的序列幀,這比寫上一組case或者if都要簡短得多。

顯示對象排序

最後是最標準的顯示對象排序問題,只是單獨排序一個物品並沒什麼技巧,從頭到尾循環並比較就對了。但是如果是將一組混亂的數據按大小排列的話,不同排序方法的差異性就會體現出來。

有的人會用最簡單的冒泡排序,但那個效率很不理想,所以有人就大張旗鼓地表示可以用分治(快速)排序來優化,但是分治排序寫起來比較複雜,不少人都沒背下來(包括我)

但實際上按時間複雜度來測試,Array.sort方法的結果看上去就很像分治排序。本來就是個系統函數,分治排序又沒有缺點,應該用的就是這個,而且是原生方法速度也很快。但深度排序需要交換層,sort是個函數,其過程無法干預。

但只要使用參數Array.RETURNINDEXEDARRAY(記得也要用Array.NUMERIC指示按數字排序,否則中途會轉換 成字符串不僅錯誤而且慢),最終就會返回一個數組,下標是原位置,值是新位置,然後根據這個數組重新執行一遍setChildIndex就行了,顯示對象 序列也就和被排序的數組完成了同步。

順便給份測試結果 ,畢竟換層有多種方案(setChildIndex, swapChildren, swapChildrenAt),整個排序主要的消耗都在交換層級上,因此換層的方式很影響效率。

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