『HUMANITY®』中村勇吾&水口哲也スペシャルインタビュー! 吉田修平が訊く“体験として新しいゲーム” – PlayStation.Blog 日本語
今このコンテキストがどれだけ理解してもらえるのか不安ではありますが、自分はかつてFlasherで、「中村勇吾」という名前を見た瞬間に上記記事を開かずにはいられませんでした。
中村勇吾氏は今も興味深いことをしていますね。
上記記事で特に興味深かったのは
「ボイド(Boids)」という群れのシミュレーションプログラム
という部分です。
Boidsに関しては今回初めて知りましたが、調べてみるとやはり興味深いものでしたので、自分でも何かやってみたいという気になりました。
成果物
参考にしたもの
【解説】動物の動きを再現する「Boids Algorithm」ってなぁに?? | cloud.config Tech Blog
アルゴリズムがどんなものかについては、検索でトップに来ていた上記ページがやはり1番わかりやすいです。
そして身もふたもない話ですが、2014年にSwiftでBoidsを実装している記事がQiitaにありました。
こちらソースがSwift4時代のものですが、アルゴリズムをコードでどう実装しているかの解説が記載されています。
Boidsアルゴリズムの実装は色々なものがありましたが、1番動きがそれっぽく、コードもシンプルな以下をそのままポーティングさせていただきました。
今回の学び
1. SwiftUIのアニメーションの向き不向き
当初はSwiftUIのカスタムアニメーションを使ってやってみようと思っていました。
しかしながら、カスタムアニメーションは「始点と終点があり、その間の動きを補完する」という用途のためのもので、今回のように「フレームごとにパラメータが変わりながらエンドレスにアニメを実行する」というのは無理そうでした。
それに実装できたとしてもパフォーマンス上の問題もあると思われます。
今後、始点と終点がないアニメに関しては素直にSpriteKitを使うことにします。
2. SpliteKitとSwift Playgroundは相性が悪い
最初コーディングはSwift Playgroundで始め、SpliteKitの簡単な動きを試してみたのですが、動きがガクガクでした。
FPSは分数になっていたと思われます。
おそらくデバッグ関係の出力が色々重そうで、Playgroundにオフにする何らかの設定があるものかと思ったのですが、特に見つからず、そういうものだと諦めてXcodeのプロジェクトでやることにしました。
3. SpliteKitのフレームの更新について
SpriteKitシーンのフレーム更新時の処理は以下のようになっています。
/// 更新処理
/// - Parameter currentTime: 現在時間
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
view?.scene?.removeAllChildren()
boids.forEach {
$0.update(boids)
view?.scene?.addChild($0)
}
}
$0.update(boids)
は要素の位置情報の更新が行われるのですが、当初はこれだけで勝手に動くものと思っていました。
/// 更新処理
/// - Parameter currentTime: 現在時間
override func update(_ currentTime: TimeInterval) {
super.update(currentTime)
boids.forEach {
$0.update(boids)
}
}
SpriteKitの物理エンジンを利用した際はノードを追加すれば勝手に動くのでその感覚でした。
実際にはフレームごとにノードを全削除し、位置情報を更新し、ノードを再描画する、という手順が必要のようです。
今回UIとしては実装しませんでしたが、下記パラメータの数値をいじるといろんな動きが表現できます。
/// 各種設定
let visualRange: CGFloat = 75
let centeringFactor: CGFloat = 0.005 // adjust velocity by this %
let minDistance: CGFloat = 20 // The distance to stay away from other boids
let avoidFactor: CGFloat = 0.05 // Adjust velocity by this %
let matchingFactor: CGFloat = 0.05 // Adjust by this % of average velocity
let speedLimit: CGFloat = 15
let margin: CGFloat = 200
let turnFactor: CGFloat = 1
visualRange: 150, minDistance: 100
本当に興味深いアルゴリズムです。