【Swift】iOSで放置型育成ゲームを作るよ(1) 〜ドット絵を一定間隔で動かす〜

# 今回のゴール
- UIImageViewにgifを描画して動かす
- UIImageView自体を1秒間に少しずつ左右に動かす

![output-ffmpeg-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/115590/ede5a1a7-de78-77a8-cb36-ff04249e84e8.gif)

# 今回のキーワード
- UIImageView
- gif
- UIButton
- CGAffineTransform
- Timer

# やったこと
## 新規プロジェクトを作成するよ
- 省略
    - アプリ名は適当にデジモン＋艦これで「Dejikore」にしたよ

## 画面を横のみを許可する
- デジモン = 横画面というイメージなので、まずは画面を横画面表示のみ許可するようにするよ
    - プロジェクトを選んでTarget → Deployment Info → Device OrientationをLandscape leftとLandscape Rightのみにチェックを入れるよ
<img width="1017" alt="スクリーンショット 2017-06-20 21.29.20.png" src="https://qiita-image-store.s3.amazonaws.com/0/115590/af697f88-3a06-d3ae-70c0-ce438341ea08.png">

## Main.storyboardにUIImageViewを置いてViewControllerと紐づけるよ
- とりあえず何も考えずにimageViewという名前で接続するよ
<img width="1017" alt="スクリーンショット 2017-06-20 21.33.37.png" src="https://qiita-image-store.s3.amazonaws.com/0/115590/bac804ca-15fb-4444-786d-27fc0dc1b032.png">

## 描画する用のgifを用意して、imageViewに紐づける
今回使う素材
![image.png](https://qiita-image-store.s3.amazonaws.com/0/115590/35fa7f9b-303b-a087-7f36-c99e9adb488a.png)
素材はクローバーラボ株式会社様ご提供の素材を利用させていただきます
ありがとうございます:pray:
http://cloverlab.jp/
http://yurudora.com/tkool/

- 素材を用意したらMacのプロジェクトフォルダ側にコピーしてからXcodeにD&Dしてファイルを追加するよ
- 追加したらインスペクタからimageViewのimageとして追加してビルドしてみるよ

## そのままじゃgifアニメが動かないのでググるよ
- 検索ワード「**UIImageView gif**」
- どうやらそのまま突っ込むだけじゃgifアニメは動いてくれない様子
- こちらの記事を参考にダウンロード・UIImage+gif.swiftをプロジェクトに追加して動かしてみるよ
    - [【Swift】GIFをUIImageViewで表示する](http://qiita.com/ShinokiRyosei/items/72595664dbdeb44df2ad)
- UIImage.gifのurlは追加した画像と同じファイル名を記述するよ
- ビルド！
- 動いた:+1:
![output-ffmpeg-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/115590/26271870-6f34-c5a2-b6b1-20d81f4e2766.gif)



```ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imageView.image = UIImage.gif(name: "character")
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
```

## このimageViewをButtonタップしたら動かすようにするよ
- とりあえずUIButtonを設置してIBOutlet接続するよ
    - <img width="1016" alt="スクリーンショット 2017-06-20 22.33.31.png" src="https://qiita-image-store.s3.amazonaws.com/0/115590/83e2bc4b-44d3-7d72-4ab7-0f4a182fd97c.png">

```viewController.swift
class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imageView.image = UIImage.gif(name: "character")
    }


    @IBAction func move(_ sender: UIButton) {
        // キャラ画像のframeを変数に格納
        var frame = imageView.frame
        
        // x方向に50ほど移動量を追加して変数に格納
        let moveX = frame.origin.x + 50
        
        // moveXを使ってframeを更新
        frame = CGRect(x: moveX, y: frame.origin.y, width: imageView.frame.width, height: imageView.frame.height)
        
        // imageViewのframeに代入
        imageView.frame = frame
    }
~~~省略~~~
```

- これでとりあえず動くけど**画面外まで行って帰ってこなくなる上に進む方向に画像が向いていない**よ・・・
![output-ffmpeg-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/115590/a1866300-eade-3a2e-c604-d1c2df23722f.gif)


## 画面内で動くように修正するよ
- 画像が右向きなのか左向きなのかを判定するフラグ「isLeft」を追加するよ
- 左を向いていればマイナス方向(←)に、逆ならプラス方向(→)に動くようにするよ

```ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    private var isLeft: Bool = true    //  キャラが左を向いているかどうかのフラグ
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        imageView.image = UIImage.gif(name: "character")
    }


    @IBAction func move(_ sender: UIButton) {
        // キャラ画像のframeを変数に格納
        var frame = imageView.frame
        
        // 移動量を定義して変数に格納
        let moveX: CGFloat = 50.0
        
        // 画面の左端(x座標が0)に到達したら
        if frame.origin.x < 0 {
            // 右を向くことを表すためにフラグをfalseにする
            isLeft = false
        
        // もしキャラのx座標が右端(画面の最大x座標 - imageViewのサイズ分)に到達したら
        } else if frame.origin.x > view.frame.maxX - imageView.frame.width {
            // 左を向くことを表すためにフラグをtrueにする
            isLeft = true
        }
        
        // 左を向いていれば左に動かすためにx軸の値をマイナス、逆ならプラスする
        isLeft ? (frame.origin.x -= moveX) : (frame.origin.x += moveX)
        
        // imageViewのframeに代入
        imageView.frame = frame
    }
```
- これで画面外に出ることはなくなったけどまだ一方向しか画像が向いてないよ
![output-ffmpeg-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/115590/a6990091-adbb-6a61-3196-65b53a0b8fd4.gif)



## imageViewをどうやれば反転させられるかをググるよ
- 検索ワード「**UIImageView 反転**」
- [UIViewを反転させる](http://appstars.jp/archive/688)の記事を参考にCGAffinetransformを使ってUIImageViewを反転させるよ
- isLeftの値が変わる時にCGAffineTransformでx軸の値を変えてあげればよさげだよ

```ViewController.swift
~~~省略~~~
        if frame.origin.x < 0 {
            // 右を向いてることを表すためにフラグをfalseにする
            isLeft = false
            
            // 右を向いたら反転させる
            imageView.transform = CGAffineTransform(scaleX: -1 , y: 1)
        
        // もしキャラのx座標が右端(画面の最大x座標 - imageViewのサイズ分)に到達したら
        } else if frame.origin.x > view.frame.maxX - imageView.frame.width {
            // 左を向いていることを表すためにフラグをtrueにする
            isLeft = true
            
            // 左を向いたら正しい方向にする
            imageView.transform = CGAffineTransform(scaleX: -1 , y: 1)
~~~省略~~~
```
これでもよさげだけど、なんとなくプロパティ監視(willSet)とか使ってみるよ

```ViewController.swift
~~~省略~~~
    private var isLeft: Bool = true {
        willSet(newValue) {
            // 新しい値がtrueだったら正しい方向に、falseだったら反転させる
            if newValue {
                imageView.transform = CGAffineTransform(scaleX: 1 , y: 1)
            } else {
                imageView.transform = CGAffineTransform(scaleX: -1 , y: 1)
            }
        }
    }
~~~省略~~~
```
- うまいこと動いた！

![output-ffmpeg-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/115590/5148070b-c488-f2af-567b-d9fccc1a0f6c.gif)


## ボタンをポチポチするのめんどくさいからTimerを使って一定間隔ごとに実行するようにするよ
- まずIBOutletされているmoveの接続を外してただのfunctionに修正するよ
- ViewControllerにstartTimerという名前の関数を作ってButtonとIBOutlet接続するよ
- ViewControllerのプロパティとしてTimerを宣言するよ
- startTimer内でtimerを初期化、timer.fire()でタイマーを動かすよ
- startTimerではタイマーが動いていればタイマーを止めて、そうでなければ再生成して動かしているよ
    - 再生成するのはinvalidateするとタイマー自体が破棄されてしまうためだよ
    - https://developer.apple.com/documentation/foundation/timer
- ついでにタップした時にボタンのタイトルを変えるようにしているよ

```ViewController.swift
class ViewController: UIViewController {

    @IBOutlet weak var imageView: UIImageView!
    private var timer = Timer()    // Timerの宣言

~~~省略~~~

    func move(_ timer: Timer) {
        // キャラ画像のframeを変数に格納
        var frame = imageView.frame
        
        // 移動量を定義して変数に格納
        let moveX: CGFloat = 50.0
        
        // 画面の左端(x座標が0)に到達したら
        if frame.origin.x < 0 {
            // 右を向いてることを表すためにフラグをfalseにする
            isLeft = false
            
            // もしキャラのx座標が右端(画面の最大x座標 - imageViewのサイズ分)に到達したら
        } else if frame.origin.x > view.frame.maxX - imageView.frame.width {
            // 左を向いていることを表すためにフラグをtrueにする
            isLeft = true
        }
        
        // 左を向いていれば左に動かすためにx軸の値をマイナス、逆ならプラスする
        isLeft ? (frame.origin.x -= moveX) : (frame.origin.x += moveX)
        
        // imageViewのframeに代入
        imageView.frame = frame
    }

    @IBAction func startTimer(_ sender: UIButton) {
        // タイマーが動いていれば
        if timer.isValid {
            // タイマーを止める
            timer.invalidate()
            sender.setTitle("start", for: .normal)
        } else {
            sender.setTitle("stop", for: .normal)
             // そうでなければ動かす
            timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(move(_:)), userInfo: nil, repeats: true)
            timer.fire()
        }
    }
```
LGTM:+1:
![output-ffmpeg-palette.gif](https://qiita-image-store.s3.amazonaws.com/0/115590/ede5a1a7-de78-77a8-cb36-ff04249e84e8.gif)


# 結果
疲れた！とりあえず今回の目標は達成！！
今日の作業ブランチはこちら
https://github.com/nasteng/Dejikore/tree/update-gif-moving

# 2017/6/22追記
次回
素材画像から一部を切り取ってgif化する
http://qiita.com/nasteng/items/a46315145abaa626adee

# 最後に
ご指摘・リファクタ大歓迎です！ :pray: