2018年6月19日火曜日

Spriteの動きをランダムにしたい

やりたいこと

Spriteの動く方向を毎回ランダムに変えたい。
SKActionだけで完結したい。

やり方

以下のようにSKAction.customAction()を使ったらうまくいった。
繰り返しはrepeatForeverを使いたかったけど、まだよくわからんのでdurationで。

let spriteNode = SKSpriteNode(imageNamed: "イメージ名")
let furafura = SKAction.customAction(withDuration: 10.0) { (node, elapsedTime) in
    let x = CGFloat(arc4random_uniform(50)) - 25
    let y = CGFloat(arc4random_uniform(50)) - 25
    print("x \(x), y \(y)")
    node.run(SKAction.moveBy(x: x, y: y, duration: 1.0))
}
spriteNode.run(furafura)

うまくいかなかったやり方

SKAction.repeatForever()と組み合わせ、SKAction.run{}を使ってクロージャの中に同様のコードを書いたら、最初に通った時に発生した乱数の値で固定されてしまい、一方向にしか動かなかった。
そもそもrepeatForever()の書くところが難しく、変な書き方するとXcodeおよびiPhone実機でアプリが終了できなくなり、iPhoneがブラックアウト、強制再起動を余儀なくされたりとおっかないことになった。なんじゃこりゃ。
詳しくはこちら

2018年6月15日金曜日

AR画面にSpriteを表示したい

Xcode9のARのテンプレートに用意されてるが、それを学習する。
ARもSpriteもよくわかってないので、時々トンチンカンかもしれませんよ。

使用ファイル


  • Scene.sks
    • Spriteを表示するための場所。Viewみたいなものだろ。場所として持ってるだけで、InterfaceBuilder的には何も設定されてないっぽい。
  • Scene.swift
    • Scene.sksに結びついてるクラス
    • 表示されたSceneがtouchされたらどうのこうの…という処理をしてるようだ
  • ViewController.swift
    • Scene.sksをロードして表示する
    • AR画面にSpriteを表示するためのARSKViewのdelegateメソッドで実際にSprite(ここではLabelNodeで👾)を表示してる

Scene.swift

touchしたらカメラの向いた方向の、カメラ位置の20cm先にARAnchorを追加する。

ARAnchorとは

ARシーンにオブジェクトを配置するために使用する、実世界の位置と方向の情報。
カメラに対する実オブジェクトまたは仮想オブジェクトの位置と向きを追跡するには、アンカーオブジェクトを作成し、add(anchor :)メソッドを使用してそれらをARセッションに追加する。
ARKitは、ワールドトラッキングセッションでplaneDetection(平面探索)オプションを有効にすると、アンカーも自動的に追加される。
ARAnchorを追加すれば、カメラの向きを変えたり、近づいたり離れたりしても、その空間上の位置を覚えててくれるわけですな。それによってそこに配置したSpriteに近づいたり離れたりできるわけだ。

ViewController

Scene.sksをロードし、ARSKViewに表示。
ARSKViewにdelegateが設定されており、SceneがtouchされてScene.swiftで新しいanchorが追加されると、ViewControllerに書かれた以下のdelegateメソッドが呼ばれ、anchor位置に👾のSpriteを表示する。

以下のARSKViewのdelegateメソッドが新しいanchorが追加されたら呼ばれるもの
func view(_ view: ARSKView, nodeFor anchor: ARAnchor) -> SKNode? { }
デリゲートに、新たに追加されたアンカーに対応するSpriteKitノードを提供するように要求します。
LabelNodeを作り、それをreturnしてるので、追加されたanchorの位置に表示している

任意の位置に表示させたい

Scene.swiftのtouchBegan()ではカメラを向けた方角の前方(設定では0.2m)に👾を表示させるべくARAnchorを追加している。
ARAnchorは4*4の行列で表されており、カメラを向けた位置の値(currentFrame.camera.transform)を別に用意した行列の変数(translation)と計算して変更している。
translationは行列の3列目のzの値に-0.2を設定し、カメラ位置の情報と演算することで前方0.2mのAnchor位置を設定している。
同じく以下のようにx,yの値を変更すれば、3次元空間上のAnchor位置を変更できる。なぜか横軸がy、縦軸がxっぽいのが納得いかないが。

AR開始時のカメラ(デバイス)の向きを基準に、以下のような意味になるようだ。

  • zは+が背後、-が前方
  • xは+が下、-が上方向
  • yは+が右手、-が左手

以下のように3列目のx,y,zを変更すれば、ある程度自由な位置に表示できる。

if let currentFrame = sceneView.session.currentFrame {
    var translation = matrix_identity_float4x4 //世界座標と向きを表す4*4の行列
    //zが奥行きの軸はいいとして、xが横軸、yが縦軸と思いきや、xが縦、yが横みたい?
    translation.columns.3.z = -0.2 //奥行き(前方0.2m)
    translation.columns.3.x = -1.0 //縦軸(上方1.0m)
    translation.columns.3.y = 0.5 //横軸(右手0.5m)
    //これは南を向いてデバイスを水平に構えた時の行列の値
    var transform = matrix_float4x4([0.0237875, -0.9977, 0.0634782, 0.0], [0.999125, 0.02154, -0.035859, 0.0], [0.0344092, 0.0642756, 0.997339, 0.0], [-0.000370748, -0.114079, -0.340914, 1.0])
    //南向き水平の値に対し、x,y,zの値を演算して変更している
    transform = simd_mul(transform, translation)

    //セッションに新しいARアンカーを追加
    let anchor = ARAnchor(transform: transform) //アンカーを作成
    sceneView.session.add(anchor: anchor) //sceneViewのsessionにアンカー追加
    //これによってViewControllerのdelegateが呼ばれる

}

2018年6月9日土曜日

点線の描画

Draw図形を描くときに点線で描く方法があるんだね。意外と簡単だった。

UIBezierPathの場合

UIViewのdraw( )の中で、以下のように書く。

//MARK:丸
let oval = UIBezierPath(ovalIn: CGRect(x: 100, y: 100, width: 200, height: 200))
UIColor.black.setFill() //stroke色
oval.lineWidth = 線幅
oval.setLineDash([10.0, 5.0, 5.0, 5.0], count: 4, phase: 0.0)

oval.stroke() //枠線描画

setLineDashがそれで、引数はUnsafePointerがどーとかわかりにくいんだけど、要は以下の3つだ。

  1. 点線の長さ、余白の長さの順に配列で指定
  2. 配列の要素数
  3. 配列の要素の使用開始位置
oval.setLineDash(UnsafePointer:<CGFloat>?, count: Int, phase: CGFloat)

CAShapeLayerの場合

こっちも似たようなもんだろう。

let layer = CALayer()
     
let lineDashPatterns: [[NSNumber]?]  = [nil, [2,3], [10, 5, 5, 5]]
   
for (index, lineDashPattern) in lineDashPatterns.enumerated() {
   
    let shapeLayer = CAShapeLayer()
    shapeLayer.strokeColor = UIColor.black.cgColor
    shapeLayer.lineWidth = 5
    shapeLayer.lineDashPattern = lineDashPattern
   
    let path = CGMutablePath()
    let y = CGFloat(index * 50)
    path.addLines(between: [CGPoint(x: 0, y: y),
                            CGPoint(x: 640, y: y)])
   
    shapeLayer.path = path
   
    layer.addSublayer(shapeLayer)
}

参考サイト

2018年6月5日火曜日

WWDC2018

ゆんべ美國でおこなわれたWWDCの開発関係の自分用まとめ。


  • iOS 12発表
    • リリースは秋
    • インストール対象はiOS11が動くデバイス
    • パフォーマンスが向上
    • Pixarと共同でAR関係の新ファイルフォーマット USDZ(ユーエスディーズィー) を開発
    • Apple純正AR物差しアプリ 「Measure」発表
    • ARKit 2発表
      • 顔認識やレンダリングその他を改良、最大4人のマルチユーザをサポート
    • その他もろもろ
  • macOS Mojave(モハーベ)発表
    • リリースは秋
    • iOSのUIKitをmacOSに導入
      • Appleからニュース、株式、音声メモ、ホームの各iOSアプリをMac用にリリース
      • 2019年にデベロッパにも提供され、iOSアプリをmacOSに移植可能に
      • iOSとmacOSの統合は否定
OSの改良でパフォーマンス向上してくれるのは歓迎。
でもまたいろいろいじらなきゃいけない点が出てくるだろーなー。仮により簡単に作れるようになったとしても、概念や扱い方が大きく違ったりすると直すのがめんどい。こちとら新しいアプリ作りたいんじゃ。過去のアプリのサポートばっかりやってられるかい!

ARKit1もまだよく使ってない。こないだiPhone買ったばかりだし。
Measureって、日本のAppStoreに同名のアプリ(個人開発)があるんだけど、いいのか? まさかそのアプリを買収するわけじゃないよね?

UIKitがMacでも使えるようになるのはいいね。扱いが統一されるとやりやすい。移植可能になるというより、移植が楽になるってこっちゃな。今までのmacOSのUIを司ってたAPIを置き換えちゃうわけじゃないんでしょ?
iOSはUIKitだけにあらずなので、そのほかのAPIというかFrameworkの互換性ってのはどんなもんなのかね?

2018年5月30日水曜日

Xcode9.3.1と9.4

一週間くらいおいて続いてリリースされたのを書いておく。

Xcode9.3.1
  • Playground編集中にXcodeが遅くなる現象の修正
  • Apple IDの再入力が必要になる現象の修正
Xcode9.4
  • Swift 4.1、iOS 11.4、watchOS 4.3、tvOS 11.4、macOS High Sierra 10.13.4に対応
  • iOS 11.4のClassKit frameworkに対応
  • バグフィックスと安定性の向上
ClassKitってのはオブジェクト指向のクラスのことじゃなく、iPadを使った学校教育の管理SDKらしい。まあ当分使うことないな。

Xcode9.4の方は、App Storeアプリでアップデートをかけても何時間も待機中で待たされている。
その間にiPadとiPhoneをiOS11.4に上げちゃったもんだから、以前のXcodeで実機確認できなくなっちゃって、えらい迷惑😠
昼間から始めて、夜10時過ぎになってようやくインストール終わったよ。待たされたのは俺だけじゃなかったみたい。

2018年5月28日月曜日

複数のジェスチャーでViewをアフィン変換

やりたいこと

ピンチやローテートのジェスチャーを複数組み合わせてView(ImageView)の形を変更したい。
ジェスチャーは複数を一度に使えるように。

やり方

UIGestureRecognizerのdelegateを設定し、delegateメソッドで複数のGestureRecognizerの同時使用を許可。
複数のアフィン変換を組み合わせる処理をしてからViewに反映させる。
ジェスチャー(例ではpinchとrotate)使用中のtransformの状態と、終了時のtransformの状態をそれぞれ保存し、ジェスチャー開始時(再開時)は前回の続きから始めるようにする。

つまづいたとこ

Swift3あたりから書き方が変わったせいで、前回のtransformの状態から再開する方法、複数のアフィン変換を組み合わせるコードに迷った。

コード(Swift4)

class ViewController: UIViewController,UIGestureRecognizerDelegate {
    
    var myView = UIImageView()
    
    //ジェスチャーで変化中のtransformを保存
    var prevRotate = CGAffineTransform()
    var prevScale = CGAffineTransform()
    //ジェスチャー終了時のtransformを保存
    var prevEndRotate = CGAffineTransform()
    var prevEndScale = CGAffineTransform()
    

    override func viewDidLoad() {
        super.viewDidLoad()
        
        myView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
        myView.image = UIImage(named: "image")
        myView.transform = CGAffineTransform.identity //念のため変形を初期化
        myView.isUserInteractionEnabled = true //ユーザーインタラクションを許可
        myView.center = CGPoint(x: self.view.frame.midX, y: self.view.frame.midY)
        self.view.addSubview(myView)
        
        let rotate = UIRotationGestureRecognizer(target: self, action: #selector(ViewController.rotate))
        rotate.delegate = self
        myView.addGestureRecognizer(rotate)
        
        let pinch = UIPinchGestureRecognizer(target: self, action: #selector(ViewController.pinch))
        pinch.delegate = self
        myView.addGestureRecognizer(pinch)
        
        //アフィン変換の初期値設定しとかないとダメ
        prevRotate = myView.transform
        prevScale = myView.transform
        prevEndRotate = myView.transform
        prevEndScale = myView.transform
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    @objc func rotate(sender:UIRotationGestureRecognizer) {
        //前回のrotateの続きからrotateする
        let nowRotate = prevEndRotate.rotated(by: sender.rotation)
        //複数のアフィン変換を組み合わせる
        myView.transform = nowRotate.concatenating(prevScale)
        prevRotate = nowRotate

        //rotate終了時のtransform状態を保存
        if sender.state == .ended {
            prevEndRotate = nowRotate
        }
    }

    @objc func pinch(sender:UIPinchGestureRecognizer) {
        //前回のpinchの続きからscaleを変える
        let nowScale = prevEndScale.scaledBy(x: sender.scale, y: sender.scale)
        //複数のアフィン変換を組み合わせる
        myView.transform = nowScale.concatenating(prevRotate)
        prevScale = nowScale

        //pinch終了時のtransform状態を保存
        if sender.state == UIGestureRecognizerState.ended {
            prevEndScale = nowScale
        }
    }
    
    //複数のrecognizerの同時使用許可
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

参考サイト

2018年5月11日金曜日

Twitter Kit SDKのサポートが終わる!?

情報ソース:ツイッター開発者ブログ

唐突だが「2018年10月31日をもって、GitHub上でiOS、 AndroidとUnityを対象にした本オープンソースに関する質問や問題を受け付けることを終了します」と公式発表された。

「Twitter Kitはみんなが簡単にツイッターコンテンツを作れるように用意したもので、去年12月にオープンソースにしてより一層盛り上げるようにしたんだけど、開発者ニーズや開発環境が大きく変わるからサポートやめたよ」という理由だという。

これって文章通りに見るなら「GitHub上での質問や問題受付のみ終了する」んだけど、まさかTwitter Kit SDKの提供や使い方の解説なんかまでやめちゃわないよな?
だってその後に、Twitter Kitを使わないでアプリにツイートを表示する方法を紹介してるんだもん。

もしアプリからツイートできなくなったりすると、かなり不便になるからなあ。