- 投稿日:2020-08-10T23:45:25+09:00
【入門】iOS アプリ開発 #2【SpriteKit を使う】
SpriteKit
iOS向けの2Dゲーム開発用として、SpriteKit というフレームワークが提供されている。
今回、古典的なピクセルアートのゲーム「パックマン」を作るため、スプライトのキャラクタをもっと簡単に扱えるようにしたい。
キャラクタは基本 16x16ドットで構成されており、テクスチャの切り替えアニメーションも考慮すると、オブジェクトで管理するのは大変そう。
そのため1つの大きなアセット画像を 16x16ドットで切り出して、テクスチャ番号で扱えるようにする。
スプライトもオブジェクト管理ではなく、スプライト管理番号で扱えるようなクラスを作成する。
Sprite Manager class の作成
スプライトを表示する API はシンプルに、
draw(0, x:100, y:50, texture:1)とする。
また一定間隔(0.1s)でアニメーションする API は、
startAnimation(0, sequence: [1,2,3], timePerFrame: 0.1, repeat: true)という感じにする。
このような API を持つ、次の CgAssetManager クラスを継承する CgSpriteManager クラスを作成する。
Asset Manager class の作成
テストで使用するアセット画像は下記で、これを 16x16ドットで切り出して、テクスチャ番号を割り当てる CgAssetManager クラスを作成する。
[spriteTest.png]
テクスチャ番号は左下から右上へ、下記のように割り当てる。
4 5 6 7 0 1 2 3 スプライト描画のプログラム
// Create a sprite manager object. let sprite = CgSpriteManager(view: self, imageNamed: "spriteTest.png", width: 16, height: 16, maxNumber: 64) // Draw a #0 sprite with #1 texture at (8,8) position. sprite.draw(0, x: 8, y: 8, texture: 1)CgSpriteManagerクラスは、"spriteTest.png" のアセット画像から 16x16ドットでテクスチャを切り出し、同時に最大64個のスプライトを描画するための管理番号を持つ SpriteManager オブジェクトを生成する。
SpriteManagerオブジェクトの draw API で、スプライト管理番号0とし、ピクセル座標(8, 8)にテクスチャ番号1のスプライトを描画する。
Background Manager class の作成
背景となるタイル描画も必要で、8x8ドットのピクセル文字も再現したい。
これらも同様に、大きな背景アセット画像から 8x8ドットのテクスチャを切り出し、テクスチャ番号で扱えるようにする。
背景のタイル描画 API は、
put(0, column: 2, row: 3, texture: 1)とする。
また、ピクセル文字を表示する API は、
putString(0, column: 2, row: 4, string: "TEST")という感じにする。
テストで使用する1つのアセット画像は下記で、SpriteManagerと同様に、8x8ドットに切り出してテクスチャ番号を割り当てる CgAssetManager クラスを継承して、CgBackgroundManager クラスを作成する。
[backgroundTest.png]
背景描画のプログラム
// Create a background manager object. let background = CgBackgroundManager(view: self, imageNamed: "backgroundTest.png", width: 8, height: 8, maxNumber: 2) // Draw a #0 background of (28x36) size at (14*8,18*8) position. background.draw(0, x: 14*8, y: 18*8, columnsInWidth: 28, rowsInHeight: 36) // Put a #1 texture on #0 background at (14,19). background.put(0, column: 14, row: 19, texture: 1) // Print text on #0 background at (8,18). background.putString(0, column: 8, row: 18, string: "SPRITEKIT TEST", offset: -16*2 /* ASCII offset */)CgBackgroundManagerクラスは、"backgroundTest.png" のアセット画像から 8x8ドットのテクスチャを切り出し、同時に最大2面の背景を描画するための管理番号を持つ BackgroundManager オブジェクトを生成する。
BackgroundManagerオブジェクトの draw API で、背景管理番号0座標(14*8, 18*8)に 28x36 のタイル面を生成・描画する。1つのタイルサイズは、テクスチャサイズの 8x8ドットとなる。
put API は、背景管理番号 0 のタイル位置(14, 19)にテクスチャ番号 1 のタイルを描画する。
putString API では、背景管理番号 0 のタイル位置(8, 18)に "SPRITEKIT TEST" のタイルを描画する。先頭文字 "S" の ASCIIコードは 83 となり、offset: -16*2 を引いた 51番のテクスチャを描画する仕組みとなる。
テスト・プログラム
GitHub に公開しているテスト・プログラムを実行すると、以下のような画面が表示され、赤モンスターがアニメーションしながら移動する。
Asset/Sprite/Background Manager クラスは、SpritekitManager.swift ファイルにコーディングしている。コメント入れて 500行未満。
GameViewController.swift では、GameSceneのサイズを (28*8, 36*8)ドットに変更している。
class GameViewController: UIViewController { private var scene: SKScene! override func viewDidLoad() { super.viewDidLoad() if let view = self.view as! SKView? { let size = CGSize(width: 28*8, height: 36*8) scene = GameScene(size: size) // Set background color to black scene.backgroundColor = UIColor.black // Set the scale mode to scale to fit the window scene.scaleMode = .aspectFit // Present the scene view.presentScene(scene) view.ignoresSiblingOrder = true view.showsFPS = true view.showsNodeCount = true } }GameScene.swift の didMove関数で Managerオブジェクトの生成と描画、update関数で赤モンスターを移動させている。
class GameScene: SKScene { private var sprite: CgSpriteManager! private var background: CgCustomBackground! override func didMove(to view: SKView) { // Create sprite and background objects. sprite = CgSpriteManager(view: self, imageNamed: "spriteTest.png", width: 16, height: 16, maxNumber: 64) background = CgCustomBackground(view: self, imageNamed: "backgroundTest.png", width: 8, height: 8, maxNumber: 2) // Draw cherries. sprite.draw(0, x: 8, y: 8, texture: 3) sprite.draw(1, x: 16*13+8, y: 8, texture: 3) sprite.draw(2, x: 8, y: 16*17+8, texture: 3) sprite.draw(3, x: 16*13+8, y: 16*17+8, texture: 3) // Draw and animate a Pacman. sprite.setPosition(4, x: 13*8+8, y: 16*12) sprite.setRotation(4, radians: CGFloat(90.0 * .pi / 180.0)) sprite.startAnimation(4, sequence: [0,1,2], timePerFrame: 0.1, repeat: true) // Draw grids on #0 background. background.draw(0, x: 14*8, y: 18*8, columnsInWidth: 28, rowsInHeight: 36) background.setDepth(0, zPosition: 0) for y in 0 ..< 18 { for x in 0 ..< 14 { background.put(0, column: x*2, row: y*2, columnsInwidth: 2, rowsInHeight: 2, textures: [4,5,6,7]) } } // Print text on #1 background. background.draw(1, x: 14*8, y: 18*8, columnsInWidth: 28, rowsInHeight: 36) background.setDepth(1, zPosition: 1) let asciiOffset = -16*2 background.putString(1, column: 8, row: 18, string: "SPRITEKIT TEST", offset: asciiOffset) // Put a #63 texture on #1 background. background.put(1, column: 14, row: 19, texture: 128) } private var x: CGFloat = 0 private var dx: CGFloat = 0 override func update(_ currentTime: TimeInterval) { // Called before each frame is rendered // Move and animate a Ghost. if x == 0 { dx = 1 sprite.startAnimation(5, sequence: [4,5], timePerFrame: 0.1, repeat: true) } else if x == 28*8 { dx = -1 sprite.startAnimation(5, sequence: [6,7], timePerFrame: 0.1, repeat: true) } x += dx sprite.setPosition(5, x: x, y: 16*6) } }参考
- 投稿日:2020-08-10T23:10:00+09:00
WWDC2020での許諾周りのアップデートまとめ
概要
今年はプライバシー、権限周りで多くのアップデートがあったのでまとめてみます。
この記事は以下のWWDC 2020セッション動画、及び公式ドキュメントを元に書いています(ここで出てくるスクショも動画のものです)。
周辺知識も含めて深く知りたい方はご参考までに。
- Build trust through better privacy
- Meet Nearby Interaction
- Design for location privacy
- What's new in location
- Handle the Limited Photos Library in your app
- AutoFill everywhere
なお、IDFAに関してはすでに多くの記事が出ているので省きます。
所感
今までよりいっそう、「必要な分だけの権限をもらう」感が強くなった
背景
- アプリがアクセスできる情報量とユーザ体験のバランスを取りやすくしたい
- iOS 13までは許諾アラートが出てきたらYesと答えないとアプリのコア機能を使えないこともあった
写真
写真共有アプリの例
iOS 13の場合、写真ライブラリ全体へのアクセス権限を求めることになる。
iOS 14からはLimited Photos Libraryが使えるようになり、ユーザが選択した写真しかアプリからアクセスできない状態を作ることができる。ちなみにフォーラムの回答によると、「選択した写真のみ」の選択肢はオプトアウトできないとのこと。さらに、今回の変更は、新しいAPIや機能を採用していないアプリでも、iOS 14端末だったら影響を受けてしまう。
「Select Photos」を選択するとこのようなピッカー(PHPickerViewController
)が表示される。このときピッカーは別プロセスで起動されており、選択し終わった写真しか知ることができない。
PHPicker
PHPicker
はUIImagePickerController
に置き換わる新しいフレームワーク。PHPickerには検索機能、複数選択機能などを備わっている。加えて、写真ライブラリへのアクセス権を必要としないので、許諾アラートを表示する必要もない。アプリがすべての写真にアクセスする必要がない限りPHPickerを使うことが推奨されている。アクセスレベル
ステータスは既存のものに加え、
.limited
ステータスが増えている。→PHAuthorizationStatus
これとは別にiOS 14からはPHAccessLevelというアクセスレベルを指定するenumが登場した。.addOnly
を指定した場合は左、.readWrite
を指定した場合は右の許諾アラートになる。
今までのステータス確認APIには更新が入り、アクセスレベルの指定が必要になっている。ユーザが
.limited
になっているかどうかは以下のように確認する。
許諾リクエスト処理は、ステータス問い合わせと同じようにアクセスレベルを指定する。これにより許諾アラートが表示されるようになる。
従来のAPIは将来的に非推奨なAPIになる。従来のAPIのままだと、ユーザが「選択した写真のみ」で許可した場合、
.limited
ではなく.authorized
を返してしまう。
基本的には.authorized
と.limited
では同じように動くべきだが、.limited
ではユーザのアルバムを作成、取得できなかったり、クラウド上のコンテンツやアルバムにアクセスできなかったりするので注意が必要。許諾範囲の変更
設定アプリから変更することができる。
新しいAPIに何も対応を入れていないアプリで、ユーザが「選択した写真のみ…」にした場合は、アプリのライフサイクルごとに一度、PhotoKitのコンテンツにアクセスする時に、現在の選択範囲を維持するか、変更するかを問うアラートが出てくる。
選択UIを手動で表示させて許諾範囲を変更する方法もある。
※ある記事によると、フォトライブラリへのフルアクセスを必要とするアプリは、ステータスが.limited
の場合、この選択UIを表示させるためのボタンを表示しないといけないといけないと書いているが、それに該当しそうな公式文章は見つけられなかった。許諾アラートを毎回表示させないためには
PHPhotoLibraryPreventAutomaticLimitedAccessAlert
をinfo.plist
に書いておけば、アクセスの拡張を促すアラートが自動で出てくることはなくなる。位置情報
近くにいる人に写真を送るアプリの例
写真を共有したい友達を見つけるために位置情報を使用するアプリを考える。iOS 13では以下の許諾アラートだった。
iOS 14ではおおよその位置情報だけをアプリと共有できる機能が追加される。Precise設定をOFFにすると、おおよその位置情報の更新のみアプリに通知される。
ユーザは設定アプリから設定を変更することもできる。
Apple Mapsの例
Apple Mapsでは「おおよその位置情報の共有」を選択した人には、アプリ起動時にマップ上部に目立つボタンを表示している。このボタンをタップすると許諾アラートが表示される。
ユーザがアプリ内を回遊し始めるとこのボタンは目立たなくなる。
App clips
App clipのユースケースに特化して新しい位置情報アクセスが設計された。App clipsでは正確な位置情報へのアクセスを行わなくても、例えば間違った店舗のウェイティングリストに入ってないかなどの確認はできる。App clipsが特定の場所で起動されたかどうかをシステムに尋ねる程度なら、許諾アラートを出す必要はない。
許諾状況の確認
「いつ」「どの程度」という2軸で許諾状況を確認することができる。
一時的に正確な位置情報をリクエストする
もし正確な位置情報を必要とする機能があるアプリで、ユーザがおおよその位置情報しか共有していない場合は、ユーザに一時的に許諾のアップデートを依頼することができる。
実装としては以下の通り。
一時的に正確な位置情報を求める理由はinfo.plist
のNSLocationTemporaryUsageDescriptionDictionary
に書いておく。
連絡先
写真共有アプリの例
手動で共有したい友達を選ぶために連絡先情報を使用するアプリを考える。iOS 13までは以下の許諾アラートが出るケースもあった。
とはいえCNContactPickerViewController
を使っていれば特に許諾を取ることなく連絡先データへのアクセスが可能だった。
iOS 14からは、TextFieldに特定のtextContentType
を指定していた場合、QuickTypeバーに連絡先データベースから取ってきた情報がサジェストされるようになる。
NearbyInteraction
NearbyInteractionフレームワークを使うときはBluetoothやネットワークアクセスの使用許可をとる必要はない。その代わり、アプリは1回限りのセッションベースのアクセスを要求する。あるユーザのデバイスと、その近くにいる別のデバイスの両方が許可をしたら、デバイス間がどれくらい離れているのか、どの方向にいるのかなどを知ることができる。この許可は、アプリがフォアグラウンドで使用され続けている限り有効になっている。
- 投稿日:2020-08-10T20:56:31+09:00
ターミナル、要らないのかな【Xcode, iOS】
今日学んだGit情報を、Outputするだけの簡素な投稿。?
今後Gitを学ぶときの為の、自分用リンクとかも貼る。ターミナル、要らないのかな。
先程、こちらの記事を読みました。
Xcode9時代のゆるふわGitHub生活先日こんな記事を書いたけど、やはりコマンドは要らないのかな。
GitHub奮闘記②【手順まとめ】でも、iOS以外の場合や、Xcode内の Git機能でも出来ないこともある場合を考えると、
コマンドは依然必要だったりするのでしょうか。ご意見あればコメントください。
鍵作成。
リモートリポジトリへのアクセス権限がなく失敗したので、鍵作成しました。
とても簡単に出来た。?Permission denied (publickey). fatal: Could not read from remote repository.「鍵」とは
作成手順
【git エラー解決策】Permission denied (publickey).
git pullは使う必要がないらしい。(fetchとmergeを使う)
pull = fetch + merge origin/mastergit fetchの理解からgit mergeとpullの役割
git fetchと git merge [リモートブランチ] で最新版を取得する。
コマンドは増えますが、より「今自分が何をやっているのか」という事が明確になり、
初心者キラーのConflictが起きた時も、具体的に何と何が衝突しているのか分かりやすくなります。
Gitの理解も深まるので、ぜひfetch - mergeの手順を覚えましょう!
UserNameと、Nameの違い。
左側のプロフィール欄にて、
Kazuki Matsumotoが「Name」で、
kazuki-userが「UserName」です。結論。
- UsernameはID、
- Nameは本名やニックネーム。
細かく。
- Usernameは重複不可で、ユーザー識別子としてプロフィールページのURLなどにも。
- Nameは重複可で、システム的にはユーザー検索時くらいにしか使われていないらしい。
ちなみに。
GithubのリポジトリURLは、
https://github.com/アカウント名/リポジトリ名
の形で構成されています。おしまい。
- 投稿日:2020-08-10T17:51:46+09:00
【Swift】オプショナル束縛構文の使い方
1.はじめに
前回オプショナル型の基本についての記事を書きましたが、今回はオプショナル型を実装するうえで、コードを読みやすく、スマートに書くことができる、オプショナル束縛構文について解説しようと思います。
前回の記事はこちらです。
(https://qiita.com/0901_yasyun/items/d64986bdfe917d90382c)2.オプショナル束縛構文とは
まず以下のif文の条件を見てください。
let year : Int? = Int("2000") if let y = year { //ここのif文の書き方に注目 print("ハレー彗星は\(y + 61)年に来る") }else{ print("エラー") }この条件の書き方は、if文とwhile文の条件部にのみ記述することができる、特別な構文です。これをオプショナル構文、またはif-let文と呼びます。
このコードだと、もし変数yearがnilではない値を持っていたら、if文の条件判定は真となります。3.コードを書く上でのメリット
通常通りであれば、まずオプショナル型の式の値がnilなのかどうかをチェックし、その後にまったくの別で、その値を使用したコードを書いていくことになります。
しかしこの書き方を使うことで、オプショナル型の式の値がnilではなかった場合、その値をチェックしたif文のthen節で、そのまま使うことができます。
4.オプショナル束縛構文の使い方
基本的な構文の使い方は、2で示したコードの通りです。
気を付けることとしては、2のコードにおいてyearの値がnilであれば、if文の条件は偽でelse節が実行されますが、その場合定数yはelse節では使うことができません。オプショナル束縛構文の条件が成立した場合、条件部で定義された変数などはthen節でしか参照することはできません。これを利用してこの変数などに、元のオプショナル型の変数などと同じ名前を使うことができます。以下のコードを見てください。
let halley : Int? = Int("2000") //定数halleyはInt?型 if var halley = halley { //変数halleyはInt型であり、then節内でのみ有効 print("ハレー彗星は\(halley)年に来た") halley += 61 print("次は\(halley)年だと予想される") }なお、先ほども述べましたが、オプショナル束縛はif文やwhile文の条件部に記述したときのみ、特別な意味を持つので注意してください。
5.オプショナル束縛と条件式
複数のオプショナル型の値を使って処理を行いたい場合、if-let文をネストすることもできますが、カンマで区切って複数の条件を並べることができます。以下のコードを見てください。
if let sapporo = Int("1972"), let nagano = Int("1998") { print("\(nagano - sapporo) years.") }オプショナル型の値だけでなく、その他の条件もカンマで区切って記述することができます。
var nagano = 1998 if nagano < 2000, let tokyo = Int("2020"), tokyo > nagano { print("\(tokyo - nagano)") //22と出力される }条件を複数書いた場合、このコードだとnaganoが2000未満の場合のみ、カンマの右側のオプショナル束縛の評価に進みます。オプショナル式がnilではなかった場合、定数tokyoにInt型の値が代入され、さらに右の条件式が評価されます。
このように、一番左側から条件の評価が始まり、条件式が偽になるか、オプショナル式がnilだった時点で評価は終了し、ifの条件は偽となります。
また、左側で値が代入された定数や変数を、右側の式で使うこともできます。おわりに
今回はコードをきれいに書く上で必須ともいえる、オプショナル束縛構文の使い方について解説しました。コードの可読性を高めることは、自分の書いたコードを世の中に発信する際にとても重要なことなので、ぜひこの書き方を利用してみてください。
- 投稿日:2020-08-10T15:58:32+09:00
[SwiftUI]R.swiftを使えるようにする
R.swiftではまだSwiftUIに対応していないのですが、本家issueに対策があったので書いてみました。
実際に使ってみたところ、かなり快適です。import Rswift import SwiftUI extension FontResource { func font(size: CGFloat) -> Font { Font.custom(fontName, size: size) } } extension ColorResource { var color: Color { Color(name) } } extension StringResource { var localizedStringKey: LocalizedStringKey { LocalizedStringKey(key) } var text: Text { Text(localizedStringKey) } } extension ImageResource { var image: Image { Image(name) } }使う際は以下のようにする。
let font = R.font.myFunnyFontBold.font(size: 35) let color = R.color.listItemTitle.color let text = R.string.localizable.hello_world.text let image = R.image.random_image.image
- 投稿日:2020-08-10T14:56:11+09:00
FlutterでAndroidビルドのみ失敗するようになる場合の対処法
現象
Androidでデバッグビルドしてデバッグ実行していたのに、突然ビルドが出来なくなることがある。
エラーは私の場合、firebase_analytics:webの依存関係が解決できない的なメッセージ。FAILURE: Build failed with an exception. * What went wrong: Could not determine the dependencies of task ':firebase_analytics:compileDebugAidl'. > Could not resolve all task dependencies for configuration ':firebase_analytics:debugCompileClasspath'. > Could not resolve project :firebase_analytics_web. Required by: project :firebase_analytics > Unable to find a matching configuration of project :firebase_analytics_web: - None of the consumable configurations have attributes. * Try: Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. * Get more help at https://help.gradle.org BUILD FAILED in 25s Finished with error: Gradle task assembleDebug failed with exit code 1環境など
ツールなど バージョンなど MacBook Air Early2015 macOS Mojave 10.14.6 Android Studio 3.6.1 Java 1.8.0_131 Flutter 1.12.13+hotfix.9 Dart 2.7.2 Xcode 11.3.1 対処法
https://github.com/FirebaseExtended/flutterfire/issues/1615
こちらにある、flutter pub cache repair
を実行して解消しました。必ず解決できるとは限らないようですが、今のところ私は現象が発生した2回中2回とも、この方法で解消しました。
- 投稿日:2020-08-10T01:50:12+09:00
Udemyのswiftコース(英語のやつ)でiOSアプリ開発を学ぶ その4
勉強するコースはこれです
https://www.udemy.com/course/ios-13-app-development-bootcamp/進捗
セクション4の途中まで終了(全部で36セクション)(ひとまずベイビーステップですよね)
サイコロのアプリを作りつつ、その中で変数(variables)と配列(arrays)について学習した。感想
配列や変数の基礎については知っているつもりだけど、良いおさらいでした
今まで自分がしてきた学習は日本語での本や授業の内容だったので、英語で学ぶとまた新鮮な部分がありますね。
「英語だとこう言うのか!」とか「英語だったらこういう風に解説すればいいのか!」とか目から鱗な内容もありました。
すでにswiftに詳しい人でも受講すると英語の勉強になっていいかも。
また、英単語や英語での表現に詳しければ英語で内容を調べやすいので、開発中に分からないところでつまづいたときにこの学習経験が役に立ちそうな気がする。ジョークは、Angela先生から昇給(a raise)と配列(arrays)をかけたジョークがnice jokeだった!
section 4で出てくるので、このコース始めた人はぜひチェックしてほしい!
笑点だったら座布団1枚もらえるやつでした笑
今後の予定
できる範囲でコースを進める。
実際のアプリ開発よりも学習することのほうに時間をかけるかも。