今回のゴール
- これからのことを考えてキャラクターをクラス化する
- ついでにステータスを表示してみる
- ViewControllerから生成の処理を剥がす
今回のキーワード
- クラス設計
- プロパティ
- initialize
- ステータス表現
やったこと
なんかちょっとリファクタしたくなった
- ViewControllerでやってる処理を整理したよ
- せっかくだしオブジェクト指向でプログラミングしよう
- キャラ用の画像を生成してUIImageViewに当て込んでTimerで定期的に動かしているけど、キャラ画像の生成とImageに当て込むのはキャラクターのクラス作ってそいつにもたせた方がよくね?
- ついでにViewControllerにキャラをプロパティとしてもたせた方がよくね?
// まずは素材画像からUIImageを宣言する
let image = UIImage(named: "sozai.png")
// 素材のimageからgifのURLが生成できたかをnilチェックする
if let url = image?.createGifURL() {
// urlが生成されていたらimageViewのimageに代入
imageView.image = UIImage.gif(url: url.absoluteString)
}
これを
// これをこんな感じにした方がよくね?
// キャラクタークラスからインスタンスを生成
let character = Character(init処理)
// ViewControllerのプロパティに設定する
self.character = character
// キャラのimageをViewControllerのimageViewにセットする
self.imageView.image = self.character.image
- ということでキャラクター生成用のクラスを作るよ
クラス設計とかしてみる
- まずはキャラクターがもつべき要素を考えるよ
- 色々考えちゃうと複雑になってしまうのでとりあえず最低限だけ
- キャラクターとは
- 名前(name)をもっている
- gif画像の情報を持っている
- 右向き・左向きのフラグを持っている
- 攻撃力、防御力などのステータスをもっている
って感じに考えてたけど、gif画像をどこにもたせてやればいいかすごく悩んだよ(というか今も悩んでるよ)
imageの情報はどこでもつべきなんだ・・??
- CharacterクラスにUIImageViewをもたせる
- ViewControllerのViewDidLoad時にそのimageViewをaddSubViewする
- ViewControllerにUIImageViewをもたせる
- ViewDidLoadでImageViewのimageにキャラクターのimageをセットする ちょうど上のコードで書いているような感じ
この二つのやり方があるかなと思ったよ
1のやり方だとmoveとstartTimerもCharacterクラスに移動させてしまえば、キャラのインスタンスが「動く」メソッドを自動的に実行してくれる形にできるかなと思ったけど、左向き・右向きの判定のためにCharacterクラスにwindowのframeを教えてあげなきゃいけなくてめんどくさそうだったので2のやり方を採用するよ(多分このへん将来的に壁にぶち当たる気がするけど、今は動くことを優先するよ)
クラス設計を基にクラスを作成していくよ
- File → New Fileを選択して「Character.swift」というファイルを作成するよ
- こんな感じで書いていくよ
import Foundation
import UIKit
final class Character {
private let name: String // 生成したら基本変更しない方針なのでletで宣言
private let image: UIImage // 生成したら基本変更しない方針なのでletで宣言
var isLeft: Bool = true {
willSet(newValue) {
imageView?.transform = newValue ? CGAffineTransform(scaleX: 1, y: 1) : CGAffineTransform(scaleX: -1, y: 1)
}
}
// 後々APIを追加した時に、レスポンスがnilだったりすることを考慮してFailable Initializerにする
init?(name: String?, charaImage: UIImage?) {
guard let name = name, let charaImage = charaImage, let gifURL = charaImage.createGifURL()?.absoluteString, let image = UIImage.gif(url: gifURL) else {
print("Error: Faild to Initialize Character")
return nil
}
self.name = name
self.image = image
}
}
isLeftいる・・・??
- ここまで書いてCharacterにisLeftのプロパティいらねんじゃねぇ・・?という疑問にぶちあたったよ
- そもそもCharacterがimageViewをもたないのでimageView.transformのあたりで当たり前にコンパイルエラーが出るよ
- てことはViewControllerで管理すればいいのかな・・?かな?
- ViewのControllerだしとりあえずそれでいっか!の精神で先に進むよ
- CharacterクラスのisLeftはとりあえず消すよ
こうなった
- せっかく名前も設定したので表示用のUILabelをstoryboardにおいてIBOutletでつないで表示できるようにしたよ
class ViewController: UIViewController {
@IBOutlet weak var imageView: UIImageView!
@IBOutlet weak var name: UILabel!
private var timer = Timer()
private var valkyrie: Character?
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)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
// キャラクターのインスタンスを生成してViewControllerのプロパティに設定
valkyrie = Character(name: "【戦乙女】ヴァルキリー", charaImage: UIImage(named: "sozai.png"))
// imageViewのimageにキャラのimageを設定
imageView.image = valkyrie?.image
name.text = valkyrie?.image
}
~~~省略~~~
なんか寂しいのでステータスのパラメーター決めて表示しようとしたけど沼にハマる
- 名前だけだとなんか寂しいので仮でパラメーターを設定して表示しようと試みたよ
パラメータ表現どうしましょ問題
- とりあえず攻撃力(Str)、防御力(Def)、運(Luc)を設定しよう
final class Character {
let name: String // 生成したら基本変更しない方針なのでletで宣言
let image: UIImage // 生成したら基本変更しない方針なのでletで宣言
let str: Int // 攻撃力
let def: Int // 防御力
let luc: Int // 運
~~~省略~~~
こんな感じで設定してもよかったんだけど、なんかもっとSwiftyに書きたくてenumとか使おうとしたよ
final class Character {
let name: String // 生成したら基本変更しない方針なのでletで宣言
let image: UIImage // 生成したら基本変更しない方針なのでletで宣言
let status: Status
// ステータスを表すenum
enum Status {
case Base(str: Int, def: Int, luc: Int)
func all() -> (str: Int, def: Int, luc: Int) {
switch self {
case .Base(let str, let def, let luc):
return (str, def, luc)
}
}
}
// 後々APIを追加した時に、レスポンスがnilだったりすることを考慮してFailable Initializerにする
init?(name: String?, charaImage: UIImage?) {
guard let name = name, let charaImage = charaImage, let gifURL = charaImage.createGifURL()?.absoluteString, let image = UIImage.gif(url: gifURL) else {
print("Error: Faild to Initialize Character")
return nil
}
self.name = name
self.image = image
self.status = Status.Base(str: 100, def: 100, luc: 100)
}
}
うーん・・・ このステータス呼び出す側が使いにくいなあ・・・ これってどうなんだ・・??
let str = character.status.all().str
そもそもステータス表現にenumは合ってない気がするぞ・・・
タプルにするか
こうするとどうだろう
final class Character {
let name: String // 生成したら基本変更しない方針なのでletで宣言
let image: UIImage // 生成したら基本変更しない方針なのでletで宣言
let status: (str: Int, def: Int, luc: Int)
// 後々APIを追加した時に、レスポンスがnilだったりすることを考慮してFailable Initializerにする
init?(name: String?, charaImage: UIImage?) {
guard let name = name, let charaImage = charaImage, let gifURL = charaImage.createGifURL()?.absoluteString, let image = UIImage.gif(url: gifURL) else {
print("Error: Faild to Initialize Character")
return nil
}
self.name = name
self.image = image
self.status = (str: 100, def: 100, luc: 100)
}
}
こうすれば呼び出し側は分かりやすい気がするよ
let str = character.status.str
でも後々ステータスの数だけTableViewのセル作りたいとかなったら困るだろうなあ・・・
タプルの要素数だけ返すことってできるんかなあ・・・
なおかつ育成ゲームだしステータス上がることを考えればgetter, setterあった方がいいんだろうなあ・・・
・・・あああああああああああ
まあいっか動くし 将来の自分が綺麗に解決してくれることを祈ります
とりあえず表示用のラベルを3つ用意してそれぞれのtextに設定するよ
~~省略~~~
@IBOutlet weak var strLabel: UILabel!
@IBOutlet weak var defLabel: UILabel!
@IBOutlet weak var lucLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// キャラクターのインスタンスを生成してViewControllerのプロパティに設定
valkyrie = Character(name: "【戦乙女】ヴァルキリー", charaImage: UIImage(named: "sozai.png"))
// imageViewのimageにキャラのimageを設定
imageView.image = valkyrie?.image
name.text = valkyrie?.name
name.adjustsFontSizeToFitWidth = true
strLabel.text = "攻撃力: \(String(describing: valkyrie!.status.str))"
defLabel.text = "防御力: \(String(describing: valkyrie!.status.def))"
lucLabel.text = "天運: \(String(describing: valkyrie!.status.luc))"
}
~~~省略~~~
それっぽくなってきたな・・・?
(Forced unwrapしてるけど表示のためだけということでご愛嬌・・・)
結果
- CharacterのinitをFailable Initializerにする意味あるんかな・・・??
- エラーの時どうするか考えていかなければ・・・
- ステータス表示すると途端にそれっぽくなるな
- さあ本格的にブログ案件になってきました
- 今日の作業ブランチはこちら
次回
最後に
ご指摘・リファクタ大歓迎です