2018年9月28日金曜日

Mojaveにしたらビルドが超遅い


macOS 10.14 Mojaveに上げたところ、Xcode10.0で実機のビルドが途中で止まってしまうようになった。(シミュレータでも同じになるかはわからんが)
Building xx of yy tasksって出て、xxが25で止まったまま動かない。
しょうがないからビルドを止めようとSTOPボタンを押してもダメ。強制終了するしかなく、Xcodeの再起動にもまたえらい時間がかかる。
しょうがないのでそのまま数分間我慢してたら、いつもと同じようにビルドできるようになったけど、他のプロジェクトをビルドすると同じように途中で長々と止まる。

その後、Macを再起動したところ、このやたら時間のかかるビルドは解消されたっぽい。

Mojave自体、日本語入力がかなりもたつくなど、10.14.0の完成度は高くなさげ。
まあしばらくしたらアップデートされるだろうけど、もうちょっとこう、最初からちゃんとしててほしい。

2018年9月13日木曜日

ARのお勉強/Sprite版

ARSessionの開始手順

AR空間に表示するSpriteや3Dオブジェクトは別として、とりあえずARSessionだけ開始する方法

SKSceneを読み込み、SKSceneViewのsceneとして表示

@IBOutlet var sceneView: ARSKView! //StoryboardのSceneViewと接続されてる

if let scene = SKScene(fileNamed: "Scene") {
sceneView.presentScene(scene)
}

Scene.swiftへのアクセスは以下のようにすればできる。
let s = sceneView.scene asScene

AR空間を認識させる

バックカメラを使い、デバイスの位置、向き、AR空間の認識などを行う。開始時に行われ、数秒間待たされる。

let configuration = ARWorldTrackingConfiguration()

ARSession開始

ARWorldTrackingConfigurationで認識したAR設定に基づいてARSessionを開始する。
sceneView.session.run(configuration)

AR空間にオブジェクトを表示させる

ARAnchor

ARのオブジェクトを表示する位置を指定するのがARAnchor。
ARAnchorをAR空間に貼り付けた後、動かせるのか、動かせないのかはまだ不明。

行列でできている

ARAnchorの値は4*4の行列(matrix_identity_float4x4)で示される。
例)以下は変数に作られたばかりの行列の値
simd_float4x4([[1.0, 0.0, 0.0, 0.0)], [0.0, 1.0, 0.0, 0.0)], [0.0, 0.0, 1.0, 0.0)], [0.0, 0.0, 0.0, 1.0)]])

その行列の4個目(0オリジンなのでindexなら3)の要素のうちx, y, zがそれぞれAR空間の軸に対応しており、デバイスを中心にしたARAnchorの位置を1m = 1.0としてFloatで指定する。
  • xは0がデバイス位置、プラスが下↓、マイナスが上↑
  • yは0がデバイス位置、プラスが右方向→、マイナスが左方向←
  • zは0がデバイス位置、プラスが後方(液晶モニタ側)、マイナスが前方(カメラの先)

指定方法

//世界座標と向きを表す4*4の行列
var transform = matrix_identity_float4x4
//行列の3列目のz値に-0.2で前方0.2mを指定
transform.columns.3.x = 0
transform.columns.3.y = 0
transform.columns.3.z = -0.2

ARSKViewDelegate

AR空間に表示するものには、2DのSpriteと3Dのオブジェクトの2つあるが、今回は2DのSprite。
ARAnchorに実際にSpriteを貼るには以下のDelegateメソッドを利用する。

新規ARAnchor追加で呼ばれる

func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { }

ARKitのテンプレートで使われてるのがこちら。
新しいARAnchorが追加されると呼ばれ、そこに貼りたいSpriteをreturnしてやればいい。

ARAnchorにSKNodeが貼られると呼ばれる

func view(_ view: ARSKView, didAdd node: SKNode, for anchor: ARAnchor) { }

SpriteのActionを使う

view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? で返すSpriteNodeにActionを設定してやればいいのだが、直接そのSpriteNodeを返してもARAnchorに固定されているため、思ったような動きをしてくれない。
そこで空のSKNodeを作り(仮にreturnNodeとする)、それにaddChildeした上で、そのreturnNodeを返してやればいい。

let returnNode = SKNode()
中略
returnNode.addChild(spriteNode)
return returnNode

Spriteを消す時には、このダミーの親ノードも消さないとゴミが残っちゃうぞ。

2018年9月10日月曜日

Spriteの衝突検出

衝突判定検出用delegateの設定

class GameScene: SKScene, SKPhysicsContactDelegate { }

SKPhysicsWorldはSprite世界に関してのクラスで、GameSceneのdidMove()などで以下のように設定しておく。
self.physicsWorld.contactDelegate = self

これによって衝突時に didBegin()メソッド が呼ばれるようになる。

エッジベースの物理体とは

ゲーム中の障害物など、重力の影響を受けないスプライト。
縁(エッジ)しか使われないということか?

GameSceneの場合、範囲として自分のframeを与えてやればいい。
self.physicsBody = SKPhysicsBody(edgeLoopFrom: self.frame)

ほかのSpriteは中心位置をoriginにした矩形になっているため、以下のようにwidthとheightの1/2だけ左上にずらしたCGRectで指定する。ちとめんどい。
self.label?.physicsBody = SKPhysicsBody(edgeLoopFrom: 
                  CGRect(x: -((self.label?.frame.size.width)! / 2),
                         y: -((self.label?.frame.size.height)! / 2),
                     width: self.frame.size.width,
                    height: self.frame.size.height))

ボリュームベースの物理体とは

ゲーム中のキャラクターなど、重力の影響を与えるスプライト。

categoryBitMaskの設定

操作する自分のキャラなのか、敵のキャラなのか、壁などの障害物なのかなどをざっくりカテゴライズすることで、衝突の影響を受けるのか、衝突時に didBegin()メソッドでチェックをするのかなどを細かに設定する。
32bitの任意のビットのみを1にすることで設定する。
以下のようにあらかじめカテゴライズ用定数を用意しておくと便利。
let rocketCate:UInt32 = 0x1 << 0
let labelCate:UInt32  = 0x1 << 1
let frameCate:UInt32  = 0x1 << 2

self.physicsBody?.categoryBitMask = frameCate
self.label?.physicsBody?.categoryBitMask = labelCate
sprite.physicsBody?.categoryBitMask = rocketCate

collisionBitMaskの設定

spriteのカテゴリーどうしの衝突(collision)の有無を決める。
これも32bitのプロパティで、categoryBitMaskの値を使って指定し、ビットが1になっているカテゴリーと衝突する。
デフォルトではすべてのカテゴリーどうしが衝突することになり、0に設定するとすり抜ける事になる。
2カテゴリー以上指定する場合は、カテゴリーどうしをor(|)でつないで指定する。
sprite.physicsBody?.collisionBitMask = frameCate | labelCate

contactTestBitMaskの設定

collisionBitMaskの設定だけでは、衝突して跳ね返ったりするものの、その検知がされないので、delegateメソッドのdidBegin()で検知する相手のカテゴリーを指定する。
sprite.physicsBody?.contactTestBitMask = labelCate

応用

collisionBitMaskを設定せず、contactTestBitMaskだけ設定すれば、すり抜けるけど衝突は検知するなんてこともできるわけだ。

衝突時のdelegateメソッド

SKPhysicsContactDelegateのメソッド。
引数のcontactで、どのspriteどうしが衝突したのか検出できる。
衝撃の値と衝突した位置も。
これを使って敵キャラを消したりするわけですな。

func didBegin(_ contact: SKPhysicsContact) {
    let bodyA = contact.bodyA
    let bodyB = contact.bodyB
    print("bodyA = \(String(describing: bodyA.node?.name)), bodyB = \(String(describing: bodyB.node?.name))")
    print("衝撃値 = \(contact.collisionImpulse)")
    print("衝突位置 = \(contact.contactPoint)")
}

撃ち落としたら消す

敵機を発射したミサイルが当たったら撃ち落とす場合は、最後に以下のようなコードを書いておく。あらかじめ敵機のnodeにenemyとでも名前をつけておく。

    if bodyA.node?.name == "enemy" || bodyB.node?.name == "enemy" {
        bodyA.node?.parent?.removeFromParent()
        bodyB.node?.parent?.removeFromParent()
    }

bodyAとbodyBのどっちが敵機、ミサイルになるのかは不定なので、どちらかが敵機だったら双方をremoveするようにする。
爆発の表現(particle)は未実装だが、remove処理の直前に処理を書けばいいだろう。

2018年8月11日土曜日

Found an unexpected Mach-O header code: 0x72613c21

問題

Xcode9の画面。
以前のバージョンでも出てた模様。
正常に動いていたアプリをAppStoreに提出すべくArchiveをし、Validate...しようとしたら
Found an unexpected Mach-O header code: 0x72613c21
という妙ちきりんなメッセージが出てニッチもサッチも行かなくなった。

意味は

訳すと「予期しない Mach-O ヘッダーのコードを発見した」である。
Mach-O(マーク・オー)はmacOSの前身であるNESTSTEP由来のコンパイラが生成するオブジェクトファイル/実行ファイルのファイルフォーマットだそうだ。(カーネルの名前がMachだからだな)
さらにMachHeaderはCPUの種類、ファイルの種類などの情報が書かれたヘッダのようだ。
要するに想定外の 0x72613c21 という情報がヘッダに書かれてるということだろう。

メッセージで検索すると 0x72613c21 の部分も含めて全く同じものが出ているようだ。人によって数値が変わるわけじゃないのだな。

どうやらこれはFrameworkをインストールした際の設定などがうまくいってないっぽい。
自分の場合TwitterKitと、画像処理のためのオープンソースライブラリのOpenCVをインストールしているので、それが関係していそう。

対処

Logファイルを見ても、エラーメッセージの後にDVTFilePathがどうのというのがいくつも出ているけど、さっぱりわからなかった。

./Users/YourFile/Library/Developer/Xcode/DerivedData の削除をしてもダメ

XcodeでProductのClean、Clean Build Folder...をしてもダメ。

解決した

TwitterKitの公式サイトに書かれていたインストール方法が、以下のようだった。
  • TwitterKitをEmbedded Binariesに
  • TwitterKitとTwitterCoreをLinked Frameworks and Librariesに
TwitterKitが2つ出てきておかしいとは思いつつ、Embedded 〜 に追加すると自動的にLinked 〜 にも追加されるんで、下の方ではTwitterCoreしか追加してなかったのだが、それが良くなかったようだ。


結果的にEmbedded 〜 の方を削除し、Linked 〜 の方にだけあらためてTwitterKitを追加したところ、無事Validate...が通った。
Embedded 〜 に残しておいても通ったけど、Linked 〜 にTwitterKitが2つ重複して入るため、気持ち悪いから消した。

原因

こちら(stack overflow)の回答を見たところ、以下のような記述があった(英語で)。
Mach-O型のFrameworkが「Static(静的)ライブラリ」である場合、それをEmbed(埋め込み)フレームワークに入れるべきではありません。 Mach-Oタイプが「Dynamic(動的)ライブラリ」の場合は、Embedフレームワークに配置する必要があります。
TwitterKitがStatic LibraryなのかDynamic Libraryなのかよくわからんのだけど、結果的にEmbedded Binaries に入れず、Linked Frameworks 〜 に入れるべきだったのだな。

とにかく解決したようなので助かった。こんなん、ネットがなかったら絶対わからんかったもん。
今後やはりEmbedded 〜 にも入れないといけない不具合が出るかもしれないけど、そしたらまた追記/訂正します。

2018年8月10日金曜日

Twitter利用の新アプリ作成時、アカウント作成要に

TwitterのApplication Managementページに載ってたのを翻訳しとく。

2018年7月現在、新しいアプリを作成する前に、Twitterデベロッパーアカウントを申請して承認を受ける必要があります。 承認されると、developer.twitter.comから新しいアプリを作成することができます。
近い将来、apps.twitter.comで既存のアプリを引き続き管理できます。 しかし、すぐにこのサイトを廃止し、developer.twitter.comの開発者ポータルにすべての開発ツール、APIアクセス、およびアプリケーション管理を統合します。 このサイトをリタイアすると、そのポータルを介して既存のアプリケーションにアクセスして管理することができます。
既存のアプリはいいけど、次からはデベロッパーアカウントを作らないといけないようだ。めんどくせえ。
詳しくは新規アプリ作った時に追記する。

Twitterアプリがないと他のアプリからツイートできない?!

問題

TwitterKitでは、Twitter公式アプリをインストールしていないデバイスで、別のアプリからツイートを投稿しようとしても、以下のようなエラーを吐いて認証ができなくなってしまった。

2018-08-10 22:19:59.259526+0900 アプリ名[72348:10180750] [discovery] errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled}
2018-08-10 22:20:01.430469+0900 アプリ名[72348:10180372] [TwitterKit] Error obtaining user auth token.

たとえばXcodeのiPhoneシミュレータでテストしようとしても、いちいち公式アプリがないといけないことになる。

原因

これはツイッター社による仕様変更のためで、ここにあるように、以前はツイッターにアプリ登録の際、
https://example.com
ってごまかしていた Callback URL なるものをきちんと登録しないといけないという。

解決法

Callback URLってナニ? ってのが本音なのだが、時間がないので解決法方だけ記す。
TwitterKitを使用する際にTwitterのApplication Managementサイトでアプリの登録を行ったわけだが、新規登録の際に得られたConsumer Key (API Key) を使った擬似的なURLを書けばOKだ。



Consumer Keyが ABCDEFGHIJK だとすれば、Settingsタブで
https://example.com
のかわりに
twitterkit-ABCDEFGHIJK://
と書けばいい。
そして下のUpdate Settingsボタンを押して完了。
これですぐにアプリから認証ができるようになる。
簡単だけどめんどくさいな。

参考サイト:TwitterがインストールされていないとTwitterKitで認証できない

2018年8月4日土曜日

TwitterKitが変更された

気づいたらTwitterKitが3.4.0にアップデートされていた。公式サイト
^ <>\|`など、一部の文字がツイートできない問題も解決しているらしいので、さっそくインストールした。(後述するが、\ (バックスラッシュ)だけはいまだツイートできなかった)

Frameworkをインストール



公式サイトに書かれた通りにやる。
  1. TwitterKit.frameworkをEmbedded Binariesに追加
  2. TwitterKit.frameworkとTwitterCore.frameworkをLinked Frameworks and Librariesに追加
とある。
しかし1.ですでにTwitterKit.frameworkはLinked Frameworks and Librariesにも自動で追加されるているので、TwitterCore.frameworkだけでいいと思われる……と思ってあらためてLinked 〜 に追加しなかったところ、AppStoreに提出する前のValidate...時に以下のエラーが出て悩むことになった。
1.のEmbedded 〜 への追加はしてもしなくても良くて、2.のLinked 〜 にTwitterKitを追加してやったら直った。注意が必要だ。

TwitterKitResources.bundleも必要



公式サイトに書かれてないけど、TwitterKitのフォルダに入ってるTwitterKitResources.bundleというエイリアスファイルもそのままAdd Files to プロジェクト名 してやらないといけない。

bridging headerが必要

これまではSwiftではふつーに
import TwitterKit
としておけば良かったが、Objective-C用しか用意されていないらしく、Objective-C bridging headerを作らねばいけなくなった。
作り方はObjective-CとSwiftのコードを混在させる方法を調べてもらうとして、その中で
#import <TwitterKit/TWTRKit.h>
としなければならない。

Twitter --> TWTRTwitterに書き換える

Twitter.sharedInstance().application(app, open: url, options: options)
などと書いていたののTwitterの部分をTWTRTwitterと書き換える必要がある。
まあこれは簡単。

Linker Errorが出る


ここまでで完璧と思ってさっそくビルドしてみたところ、Apple Mach-O Linker (ld) Errorなどというエラーが出てしまった。
必要なFrameworkがインストールされていないか、必要ファイルへのpathが狂っているなどの場合に出るらしい。

MKMapViewとかMKMapView、_kUTTypeImageなどというメッセージからMapKit.frameworkとMobileCoreServices.frameworkが必要と判断して追加したら消えてくれた。
そんなことどこに書かれてんだよ。ツイッター、この野郎。

とりあえずビルドできるようになったのは幸い。

バックスラッシュだけはまだ送信できない

上述の通り、^ < >など一部の文字が含まれているとツイートできない問題は直っていたが、\(バックスラッシュ)だけはいまだツイートできなかった。
\(^o^)/ などというように顔文字で使われることがあるから早く直してくれないと困るな。制御文字を扱う際のprefixに使われてるせいじゃないかと思うが、ツイッター公式アプリやWeb版のツイッターじゃ問題ないんだからツイッター社の怠慢。