【SwiftUI】RPGにあるような攻撃エフェクトを再現してみたい!


はじめに

こんにちは!@kanato4です。

仕事ではJavaがメインなのですが、プライベートではSwiftを勉強している若輩者です。ただ、Swiftははじめて間もないので基本的な文法しか分かりません。これからもっと知識をつける予定です。

さて、そんなSwift初心者の私ですが、何を思ったのか「思いきってSwiftUIを使って遊んでみよう!」と考えたわけです。

SwiftUIは2019年に発表されたフレームワークなのですが、従来のSwiftの書き方とかなり違ってくるという話だったので、「やっぱこれからiPhoneアプリ作るなら知っておかないとね☆」と挑戦したわけですが、案の定苦労しました。。

今回はそんな私がはじめてSwiftUIで作れた成果物?を折角なら見てもらいたいなぁと思い記事にしました。アウトプット大事!

完成したもの

まずは完成したものを見てやってください。

内容は簡単なもので、ボタンを押すと爆発するアニメーションが表示されます。同時に、真ん中のイラストもボタンを押す前後で変化するようにしました。もういちどボタンを押すとリセットされます。

爆発は繰り返される事なく、一回だけ爆発して終了します。(意外とここで躓いた)

正直、Swiftを学び始めて間もないのでこれだけでも結構苦労しました。。
まだまだ課題は山積みといった感じですが、とりあえず形になって良かったなという感想です。

実際のコード

実際に実装したコードがこちらになります。

ContentView.swift
import SwiftUI

struct ContentView: View {
    //ステートプロパティ
    @State private var trigger = false

    var body: some View {
        VStack {
            Text("爆破スイッチ")
                .font(.largeTitle)
            Spacer()
            ZStack{
                //三項演算子(条件式 ? true時の値 : false時の値)
                Image(self.trigger ? "computer_note_bad" :"computer_man")
                    .resizable()
                    .scaledToFit()
                    .frame(width: 200, height: 200)
                //triggerがtrueだったら
                if trigger{
                    //構造体を呼び出す
                    LoadingView()
                }
            }
            Spacer()
            Button(action: {
                //クロージャ内ではプロパティを参照する際には自分自身を指すselfを指定
                //toggleメソッドはブール値(Bool型)の値を反転させる
                self.trigger.toggle()
            }) {
                //三項演算子(条件式 ? true時の値 : false時の値)
                Text(self.trigger ? "もういちど" : "ばくはつ!")
            }
            .font(.largeTitle)
            .foregroundColor(.red)
            .padding()
        }
    }
}

struct LoadingView: View {
    //ステートプロパティ
    @State private var index = 0
    //mapで1~16の数字に処理を適用し、その処理を施した配列imagesを作成する
    private let images = (1...16).map { UIImage(named: "explotion_\($0)")! }
    //指定された時間の間隔で現在の日時への接続を繰り返す
    private var timer = Timer.publish(every: 0.05, on: .main, in: .default).autoconnect()

    var body: some View {
       return Image(uiImage: images[index])
           .resizable()
           .scaledToFit()
           .frame(width: 300, height: 300, alignment: .center)
           //パブリッシャー(timer)によって発行されたデータを検出したときの処理
           .onReceive(
               timer,
               perform: { _ in
                   //クロージャ内ではプロパティを参照する際には自分自身を指すselfを指定
                   self.index = self.index + 1
                   if self.index >= 16 {
                       //timerの自動接続を停止
                       self.timer.upstream.connect().cancel()
                       self.index = 0
                   }
               }
           )
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

使用した画像はこのような感じです。

コード中のtoggleメソッドでcomputer_mancomputer_note_badを切り替えています。

爆発エフェクトの画像はexplotion_1からexplotion_16に分けています。
explotion_1には、いわゆる透明な画像が入っている状態ですね。

@State private var trigger = false

@Stateと書かれている部分はステートプロパティと呼ばれるものです。
この属性を宣言する事でSwiftUIがプロパティを自動で監視してくれて、その値が変更されると自動でビューを更新してくれる偉いやつです。ステートプロパティへのアクセスはコンテンツビューの内部からのみに限定される事が望ましいため、privateと言うアクセス修飾子を指定しています。また、privateの装飾子を指定する事で外部からアクセスができなくなるため、必ず初期化をする必要があります。

アニメーションの再現方法としては、番号が振ってある爆発の画像を変数imagesに格納し、timerが接続されるたびにindexの数字が加算されていき、indexの数字に対応した爆発の画像をViewに返すという処理を繰り返して再現しています。

また、アニメーションの処理が記述されている構造体のif文の次の1文が大事。

self.timer.upstream.connect().cancel()

この1文で接続をキャンセルしないと永遠と爆発してしまいます。

ノートPCが可哀想ですね。。

ここで、爆発の画像の最初の1つ(explotion_1)を透明の画像にした理由なのですが、上記の1文でキャンセルするとアニメーションは停止するのですが、最初のアニメーション画像が呼び出された状態でストップしてしまいます。

explotion_1に爆発の画像を入れると中途半端に爆発して止まってしまいます。。

そのため、最初の画像は透明にしてます。単にコードの出来がよくない可能性もありますが、やむなくこのような形にしました。無念。

おわりに

滅茶苦茶読みづらかったでしょうが、ここまでお付き合いいただきありがとうございました。

Swiftは分からないことだらけですが、きちんとアウトプットをやっていって、ゆくゆくは個人開発したものをストアで公開できるぐらいになりたいと思います。

またちょこちょこSwiftUIで遊んでみたいなぁ。