15.5.4 創建太陽系的動畫

15.5.4 創建太陽系的動畫

至此,我們已經實現了動畫庫的所有類型和函數。雖然我們使用的是 F# Interactive,但是,把庫的核心部分保存到一個單獨的文件是個好主意(例如,Animations.fs)。然後,我們可以加載整個庫,使用 #load 指令,將文件名作爲參數。這種方式,也確保該內部類型擴展(包括我們添加到行爲的運算符)正確加載,因爲,內部類型擴展必須用相同的命令處理,作爲它們擴展的類型。

動畫的關鍵部分是對象彼此繞着旋轉。我們剛剛創建的庫可以把現有的基元組合成高級基元,解決特定的問題。我們可以把旋轉封裝到可重用的函數(或 C# 方法)中,稍後會用來描述仿真。這是一個精心設計的函數庫的非常重要的屬性,處理序列的函數按照相同的方式是可組合的。

我們新的基元實現動畫圍繞點 (0,0)、按指定的距離和速度旋轉。清單 15.21 顯示了這個實現。考慮到事情的複雜性,需要很少的代碼。

Listing 15.21 Implementing rotation in F# and C#

// F# function
let rotate (dist:float32) speed img =
let pos = wiggle * dist.forever
img |> translate pos (wait 0.5f pos)
|> faster speed

// C# extension method
public static Behavior<IDrawing> Rotate
(this Behavior<IDrawing> img, float dist, float speed) {
var pos = Time.Wiggle * dist.Forever();
return img.Translate(pos, pos.Wait(0.5f))
.Faster(speed);
}

有點令人驚訝的是,我們只使用 translate 函數,就可以實現旋轉。使用 wiggle 基元創建的運動是正弦波,這意味着,它給了我們旋轉對象的一個座標值。爲獲得第二個座標值,我們需要延遲 0.5 秒的相位。如果使用餘弦函數創建類似的基元,我們會得到希望的值。爲了延遲行爲,可以使用我們剛剛實現的 wait 函數。

我們可以使用流操作來指定完成動畫應進行的操作順序。指定旋轉之後,還應用了 faster 函數,指定所需的旋轉速度。在 C# 中,我們可以使用相同的編程風格,由於使用了擴展方法,把動畫作爲第一個參數,返回一個新動畫作爲結果。這是類似於在 LINQ 查詢中應用多個運算符(過濾、投影、分組)。

使用這個基元來描述旋轉,現在可以很容易地創建太陽系動畫了。我們首先創建三個圓代表太陽、地球和月亮,然後,描述它們如何彼此圍繞轉動。圖 15.6 顯示了正在運行的動畫,可以看到我們創建的內容。

image

圖 15.6 運行中的太陽能系模擬 ;月球圍繞地球旋轉,它們兩個圍繞太陽旋轉。

現在讓我們看一下代碼。清單 15.22 顯示了兩種語言的實現,因此,可以看到在 F# 和 C# 中,相關的構造如何彼此對應。

構造行星的代碼非常簡單。唯一值得注意的事情,是我們將使用一個圓基元來創建動畫,因此,必須提供畫筆和大小作爲行爲。這意味着,可以產生有趣的效果,例如,創建的太陽逐漸變大,並隨着時間的推移更改顏色。

Listing 15.22 Creating solar system animation in F# and C#

// F# version
let sun = circle (forever Brushes.Goldenrod) 100.0f.forever
let earth = circle (forever Brushes.SteelBlue) 50.0f.forever
let moon = circle (forever Brushes.DimGray) 20.0f.forever

let planets =
sun --
(earth -- (moon |> rotate 40.0f 12.0f)
|> rotate 160.0f 1.3f)
|> faster 0.2f

// C# version
var sun = Anims.Circle(Time.Forever(Brushes.Goldenrod), 100.0f.Forever());
var earth = Anims.Circle(Time.Forever(Brushes.SteelBlue),50.0f.Forever());
var moon = Anims.Circle(Time.Forever(Brushes.DimGray), 20.0f.Forever());

var planets =
sun.Compose(
earth.Compose(moon.Rotate(50.0f, 12.0f))
.Rotate(150.0f, 1.0f))
.Faster(0.2f);

從旋轉的對象來組合動畫,更加有趣。我們先來從中間解釋一下。使用 rotate 函數來創建圍繞中心在 50 像素距離上旋轉的月亮。再把這個動畫與不旋轉的地球組合起來,所以,結果就是月球繞地球旋轉。這個結果的類型是一個動畫,所以,我們可以再次啓動,這一次它(組合的動畫)在 150 像素的距離上旋轉。當我們再把這個動畫與太陽(不移動)組合起來,就得到了地球圍繞太陽旋轉的動畫了。最後,我們使用 faster 基元,改變動畫的速度。我們實際上降低了速度,因爲我們使用的倍速數小於 1。注意在 F# 中,可以首先使用流操作符來寫動畫對象,然後,做各種轉換。

我們實現的動畫只有三個對象,但容易看出,如何添加其餘的行星。框架的可組合性意味着,增量更改保持簡單,即使隨着總體結果變得越來越複雜。

更進一步的動畫庫

這個庫還可以有很多有趣的補充。我們已經提到,可以添加更多繪圖基元和轉換。添加新基元的提升版本,將能夠輕鬆地創建動畫。還有其他更有趣的選項。

我們可以實現使用了其他上下文信息的行爲。我們已經在 BehaviorContext 的類型中封裝了上下文,因此,添加額外的信息將相當簡單。我們可以在上下文中添加到當前光標位置,能夠創建動畫的形狀追或逃離光標。

一個更復雜的擴展是能夠創建非線性的動態系統。我們可以添加一個基元,告訴我們某些行爲的值更改的速度有多快,可以根據其狀態改變的速度,再用它來創建一個系統。

動畫庫提供了以函數式編程風格實現庫的示例。你可能想知道,這種風格如何轉化到更傳統的業務應用程序的開發中。讓我們通過另一個示例快速瀏覽一下,不會像討論動畫庫一樣詳細,而是應該給你一個概念,即業務聲明庫(或 DSL)感覺可能像什麼。