はじめに
SwiftUIを使ったゲームを作っていて、もの(ImageView)を投げる動作を作りたいと思いまして。
放物線でImageを移動させる方法が調べても出てこないので実装を考えて作ってみました。
なお、放物線といっていますが射法投射などの厳密なものではなく(数学苦手なのでわからない)
何となくふわっと移動すると捉えていただければと思います。
1. 作ったもの
こちらになります。
ふわっと動いていますね。
![]() |
---|
2. まずは直線に動かしてみる。
まずは、横直線に動くアニメーションを作ってみます。
動作としては指定した値(width)の分Imageを横に移動させます。
実装については、画像の通り移動量widthを10分割し、一回の移動量(dx)とします。
そしてtimerを使い一定の秒数毎にx座標をdx分移動するようにします。
コードはこちらです。
import SwiftUI
struct ContentView: View {
// Viewの現在の座標
@State private var x : CGFloat = 30
@State private var y : CGFloat = UIScreen.main.bounds.size.height * 0.8
func move(width: CGFloat) {
// 1回の移動量
let dx = width / 10
// ImageViewの初期座標
let ofX : CGFloat = 0
let ofY : CGFloat = UIScreen.main.bounds.size.height * 0.8
// 初期位置ofXからdxずつ10回移動する。
var i = 1
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true){ timer in
self.x = ofX + CGFloat(i) * dx
i += 1
if i > 10 { timer.invalidate()}
}
}
var body: some View {
VStack {
Image("en").resizable()
.frame(width: 50.0, height: 50.0, alignment: .leading)
.position(x: self.x, y: self.y)
.animation(.easeOut)
Button(action: {
// ボタンを押すとwidthに指定した分横に移動させる。
self.move(width: 300)
}
, label: {
Text("Button")
})
}
}
}
移動する動作はmove関数を作成し、移動量を引数として渡すようにしました。
withTimeInterval
の値を変えることで移動する速さを調整できます。
ここも引数でもいいかもしれません。
挙動を確認
![]() |
---|
3.円に沿って移動させる
続いていよいよ縦にふわっと移動させてみます。
ここでは円を考えてそれに沿って移動する実装としたいと思います。
半径 r = width / 2の円を考える
イメージはこちらです。
widthを直径とする円を考えてその円に沿ってy座標を更新します。
式
上記の図は原点をrだけずらしているので円の式は
(x - r)^2 + y^2 = r^2
これをyについて解くと
y = \pm\sqrt{r^2 - (x - r)^2}
座標として欲しいのはこれのプラス側ですので絶対値を取れば良さそうです。
実装
move()関数を次のように修正します。
func move(width: CGFloat, height: CGFloat) {
// 一回の移動量
let dx = width / 10
// Viewの初期座標
let ofX : CGFloat = 0
let ofY : CGFloat = UIScreen.main.bounds.size.height * 0.8
// 直径widthの半径r
let r = width / 2
var i = 1
Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true){ timer in
self.x = ofX + CGFloat(i) * dx
self.y = ofY - abs(sqrt(pow(r,2) - pow(CGFloat(i)*dx - r,2)))
i += 1
if i > 10 { timer.invalidate()}
}
}
self.yに先ほど求めた式を代入しています。
注意点としてはiPhoneの画面は左上が原点となりますのでy軸を上に移動させたい時はy座標をマイナスしていきます。
そのためyの初期座標ofYから円の式で求めた数値を引いています。
self.y = ofY - abs(sqrt(pow(r,2) - pow(CGFloat(i)*dx - r,2)))
挙動を確認
![]() |
---|
ふわっと移動できました!!
ただ完全な円だと投げる動作としては不自然な感じがありますね。
高さを微調整
対策としてはy座標に適当な数をかけてスケールすることによってそれっぽく見せています。
self.y = ofY - abs(sqrt(pow(r,2) - pow(CGFloat(i)*dx - r,2))) * 0.6
![]() |
---|
以上です!
ちょっと無理矢理なやり方ですがいい方法がありましたらコメントいただけると嬉しいです!