2017年9月2日土曜日

SpriteKit に Firebase/Admob を導入してみた

概要

SpriteKit で開発したゲームに Firebase/Admob を導入してみました
今回は GADBannerView でバナー広告を表示するのではなく全画面に表示される Interstitial Ads を導入してみました

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)
  • Firebase 4.0.2
  • Google-Mobile-Ads-SDK 7.20.0

ライブラリインストール

  • pod init
  • vim Podfile
pod 'Firebase/Core'
pod 'Firebase/AdMob'
  • pod install

で .xcworkspace を開きます
ついでに GoogleService-Info.plist をプロジェクトにコピーしておきましょう

AppDelete.swift で初期化

これはいつも通りです

import Firebase

でインポートして didFinishLaunchingWithOptions で初期化します

FirebaseApp.configure()

GameViewController.swift で広告を生成

まず UIViewController 側で広告の初期化や生成を行うメソッドを準備します

import GoogleMobileAds

でインポートします
GameViewController に GADInterstitialDelegate を継承しましょう

class GameViewController: UIViewController, GADInterstitialDelegate {
    ...
}

そしてインタースティシャル広告用の変数を定義します

var interstitial: GADInterstitial!

Interstitial Ad を初期化

次に interstitial を初期化する関数を定義します

// Interstitial Ad を作成します
func createAndLoadInterstitial() -> GADInterstitial {
    let interstitial = GADInterstitial(adUnitID: "ca-app-pub-3940256099942544/4411468910")
    interstitial.delegate = self
    let request = GADRequest()
    request.testDevices = [ kGADSimulatorID ];
    interstitial.load(request)
    return interstitial
}

ここは特に説明する必要はないと思います
継承した GADInterstitialDelegate を interstitial.delegate に設定しましょう

広告を表示する関数を追加

次に広告を表示する関数です

// 広告を表示します
func showAd() {
    if interstitial.isReady {
        print("showAd")
        interstitial.present(fromRootViewController: self)
    } else {
        print("Ad wasn't ready")
    }
}

print 分はデバッグなので削除して OK です
この広告の呼出し方法も決まり文句みたいなものなのでほぼそのまま使えば OK です

広告が非表示になったコールされる関数を定義

インタースティシャル広告の場合画面全体に広告が表示されます
Admob の場合閉じるボタンも勝手に生成してくれます
ただ、インタースティシャル広告は一度表示したら再度生成しなおす必要があります
なので、広告が非表示になったタイミングで再度生成します

// 広告が非表示になった場合に再度生成します
func interstitialDidDismissScreen(_ ad: GADInterstitial) {
    print("interstitialDidDismissScreen")
    interstitial = createAndLoadInterstitial()
}

この関数は GADInterstitialDelegate を継承していれば勝手にコールされる関数になります
呼び出されない場合は delete をちゃんと設定しているかなど確認してください

viewDidLoad でシーンに移動する

ここまでできたらシーン呼び出しましょう
その際にシーン側で定義した viewController を管理する変数にこの GameViewController を設定しましょう
こうすることでシーン側で GameViewController で定義した showAd 関数を呼び出すことができるようになります

override func viewDidLoad() {
    super.viewDidLoad()
    // 広告の初期化
    interstitial = createAndLoadInterstitial()

    if let view = self.view as! SKView? {
        if let scene = FirstScene(fileNamed: "FirstScene") {
            scene.scaleMode = .aspectFill
            // ここで GameViewController をセットする
            scene.viewController = self
            view.presentScene(scene)
        }
        view.ignoresSiblingOrder = true
        view.showsFPS = true
        view.showsNodeCount = true
    }
}

シーンファイルから呼び出す

ではシーンファイル側から広告を呼び出してみましょう
.sks ファイルにはラベルとボタン用の SKSpriteNode を 1 つ配置しました
あとは touchDown されたときに showAd をコールすれば広告が表示されます

import SpriteKit

class FirstScene: SKScene {

    // show ボタン
    var show = SKSpriteNode()
    var viewController: GameViewController!

    override func didMove(to view: SKView) {
        // show ボタンの初期化
        show = self.childNode(withName: "show") as! SKSpriteNode
    }

    func touchDown(atPoint pos : CGPoint) {
        if let node = atPoint(pos) as? SKSpriteNode {
            if(node == show){
                if let _ = self.view {
                    viewController.showAd()
                }
            }
        }
    }

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        for t in touches { self.touchDown(atPoint: t.location(in: self)) }
    }
}

動作確認

こんな感じで表示されれば OK です
spritekit_with_admob.gif

今回の場合非表示ボタンを押した後に再度インタースティシャル広告を生成しているので何度押しても表示されます
もし再生成していない場合は Ad wasn't ready となり広告も表示されません

最後に

SpriteKit + Admob (Interstitial Ad) をやってみました
バナービューの場合は storyboard を使って GADBannerView を生成するだけですが、ずっと表示されてしまうという問題もありインタースティシャル広告を使ってみました

ポイントは UIViewController 側で生成した広告をシーン側で呼び出す部分かなと思います
だったら初めからシーン側で広告も生成すればいいじゃんと思いますが、Admob の広告は基本的に View なのでシーン側で表示するとなるといろいろと大変です
あとはルールと言うか役割的な話で View はちゃんと UIViewController 側で表示したほうが良いです

参考サイト

2017年9月1日金曜日

SKPhysicsJointFixed で結合するときのちょっとしたテクニック

概要

SKPhysicsJointFixed で 2 つの SKSpriteNode を結合するときにノードが小さかったりスピードが早かったりするとノード同士が重なったり、あまりに離れた距離で結合してしまうケースがあります (というか自分はありました)
で、そんなときに使えるテクニックを紹介します

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)

2 ノード間の距離を測定して結合する

まずノードの衝突を判定するには SKPhysicsContactDelegate を継承したクラスに didBegin, didEnd というイベント用の関数を追加することで判定することができます
2 つの関数は衝突した点を管理する SKPhysicsContact を引数として扱うことができます

そしてこの SKPhysicsContact から衝突した 2 つのノード情報も取得することができます

let a = contact.bodyA
let b = contact.bodyB

bodyA が衝突されたノードで bodyB が衝突したノードになります
これら 2 つのノード間の距離を三平方の定理を利用して計測します
とその前に CGPoint を拡張して 2 ノード間の距離を測定する関数を定義します

extension CGPoint {
    func distanceFromCGPoint(point: CGPoint) -> CGFloat{
        return sqrt(pow(self.x - point.x,2) + pow(self.y - point.y,2))
    }
}

これを使って didBegin が呼び出されたときに以下のように距離を測定します

let dist = a.node?.position.distanceFromCGPoint(point: (b.node?.position)!)

そして、この計算した距離がある一定以上かつ一定以下であった場合に SKPhysicsJointFixed を使って結合すれば OK です

let distMin: CGFloat = 42.0
let distMan: CGFloat = 66.0

if dist! >= distMin && dist! <= distMax {
    let joint = SKPhysicsJointFixed.joint(withBodyA: a, bodyB: b, anchor: contact.contactPoint)
    self.physicsWorld.add(joint)
}

と自分はこんな感じで 2 ノードを結合するようにしています

最後に

SKPhysicsJointFixed で結合するときには 2 ノード間の距離を測定してから結合すると良いよというネタを紹介しました
この方法は衝突が頻繁に発生する場合には使える方法だと思います
そうではなく 1 回しか発生しない場合などはわざわざ計算しなくても良いかもしれません

また、この方法の弱点としてはノードが三角形や四角形の場合に頂点で衝突が発生した場合と辺で衝突が発生した場合に条件が一緒だということです
本当は辺と頂点で結合する条件を変更したほうがいいのですが、現状の Swift3 の物理エンジンを使った衝突判定だとそこまではできないようです

他にもテクニックはあると思うので参考程度に見てもらえると良いかなと思います

参考サイト

2017年8月30日水曜日

DeepSecurity の SOAP API を ruby で嗜む

概要

DeepSecurityManager には REST と SOAP の API があります
現状だと REST よりも SOAP の方ができることが多いです
公式で Java のライブラリが提供されているのですが Java でコールする気にはなれなかったので Ruby からコールしてみました
SOAP 用の Ruby ライブラリは Savon というライブラリを利用しています

環境

  • Ubuntu 16.04
  • ruby 2.3.1p112
  • gem 2.5.1
  • Savon 2.11.2
  • DeepSecuritManager 10.0

事前準備

事前に DeepSecurityManager で API を有効にする必要があります

Administration -> System Settings -> Advanced

で「SOAP Web Service API」と「Status Monitoring API」を Enabled にします
Status Monitoring API は REST API で今回は使用しないので Enabled にしないでも大丈夫です

ライブラリインストール

  • apt install zlib1g-dev
  • bundle init
  • vim Gemfile
gem "savon"
  • bundle install

で vendor 配下にインストールされました

サンプル

まずは DeepSecurityManager にアクセスして wsdl の情報を表示してみます

  • vim show_wsdl.rb
require 'savon'

client = Savon.client(
        :wsdl => 'https://192.168.100.8:4119/webservice/Manager?WSDL',
        :ssl_verify_mode => :none
        )
puts client.operations
  • bundle exec ruby show_wsdl.rb

wsdl は DeepSecurityManager 自体が配信しているのでそれを指定します
今回は SSL 通信ではないので :ssl_verify_mode を :none に設定します

これで実行すると SOAP API のオペレーションの一覧がずらーっと表示されると思います
190 ほどあると思います

認証してみる

ユーザ名とパスワードで認証してみましょう

  • vim auth.rb
require 'savon'

client = Savon.client(
        :wsdl => 'https://192.168.100.8:4119/webservice/Manager?WSDL',
        :ssl_verify_mode => :none
        )
response = client.call(:authenticate, message: { username: 'username', password: 'password'})
puts response.body[:authenticate_response][:authenticate_return]
  • bundle exec ruby auth.rb

で他の SOAP API をコールするためのセッショントークンを取得することができます

ポリシーの一覧を取得する

ポリシーの一覧を取得してみましょう

  • vim get_policies.rb
require 'savon'

client = Savon.client(
        :wsdl => 'https://192.168.100.8:4119/webservice/Manager?WSDL',
        :ssl_verify_mode => :none
        )
response = client.call(:authenticate, message: { username: 'username', password: 'password'})
sid = response.body[:authenticate_response][:authenticate_return]

response = client.call(:security_profile_retrieve_all, message: { sID: sid })
ret = response.body
ret[:security_profile_retrieve_all_response][:security_profile_retrieve_all_return].each { |r|
  puts r[:id]
  puts r[:name]
  puts r[:description]
}
  • bundle exec ruby get_policies.rb

でポリシーの一覧から名前と説明を取得することができます

最後に

DeepSecurity の SOAP API を Ruby からコールしてみました
今回は Savon というライブラリを使用しましたが Ruby には他にも SOAP API 用のライブラリがあります
はじめは soap4r というコードジェネレータにもなるツールを使っていたのですがいろいろとエラーが出たのでやめました
最終的には Savon に落ち着きました
現在もメンテナンスはされていそうです

DeepSecurity の SOAP API はかなりたくさんあるのですが、WSDL しか情報がないのでどの API がどの操作に対応しているのか探すのが結構たいへんです
オペレーション名から何ができるか推測して、必要なパラメータを WSDL から生成して実際にコールするしか現状ではなさそうです

2017年8月29日火曜日

QuickTime で iPhone の動画を録画する

概要

LadioCast + soundflower を使った王道です
よく音が出なかったり録画の手順を忘れるので備忘録に残しておきます

環境

  • macOS X 10.12.6
  • QuickTime Player 10.4 (894.12)
  • LadioCast 000012003 (1649)
  • soundflower 2.0b2

手順

  1. iPhone を Mac に接続
  2. マナーモードを解除する
  3. LadioCast 起動
  4. LadioCast の「出力メイン」が内蔵出力になっているかつ音量が Max になっていることを確認
  5. LadioCast の「入力メイン」が Soundflower (2ch) になっていることを確認
  6. Mac のスピーカーの出力を内蔵スピーカーから Soundflower (2ch) に変更する
  7. QuickTime Player 起動
  8. ファイル -> 新規ムービー収録
  9. カメラ -> iPhone を選択
  10. マイク -> iPhone を選択
  11. QuickTime Player の音量を調整する
  12. 録画ボタンを押して録画開始

です
いつも忘れるので備忘録を残しておきます
事前に LadioCast (App Store から) と Soundflower (Github から) はインストールしておいてください

2017年8月28日月曜日

SpriteKit で iPad の画面表示に対応する方法

概要

タイトルの通りです
.aspeftFill を使っていたのですが、それで iPad で表示すると上下に黒の帯が表示されてしまいました
どうやらこの状態で審査に出すとリジェクトされるらしいので iPad で黒帯が表示されないように対応してみました

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)

対応コード

シーンに遷移する箇所を以下のように修正しました

if let view = self.view as! SKView? {
    if let scene = TitleScene(fileNamed: "TitleScene") {
        if (UIDevice.current.model.range(of: "iPad") != nil) {
            scene.scaleMode = .fill
        } else {
            scene.scaleMode = .aspectFill
        }
        view.presentScene(scene)
    }
    view.ignoresSiblingOrder = true
    view.showsFPS = true
    view.showsNodeCount = true
}

ポイントは iPad だったときに scaleMode を .fill にするところです
それ以外はこれまで通り .aspectFill を使います

こうすることで iPad 時にも画面いっぱいに SKScene を表示することができます

最後に

iPad 時に画面に黒帯が表示されてしまう状況に対応してみました
まだ審査に出してはいないのですが、おそらくこれで iPad 対応として審査が通ると思います

2017年8月26日土曜日

個人的 App Previews とスクリーンショットの作成方法

概要

アプリを審査に出す時に必要になる動画 (App Previews) とスクリーンショットを取得するコツを紹介します
自分は実機を使わずにすべてシミュレータを使っています

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)
  • iMovie 10.1.6

流れ

  1. iPhone 7 Plus のシミュレータでスクリーンショット取得
  2. iPhone 7 Plus のシミュレータで動画撮影
  3. iPad Pro (12.9-inch) のシミュレータでスクリーンショット取得
  4. iPad Pro (12.9-inch) のシミュレータで動画撮影
  5. 2, 4 で撮影した動画を iMovie の App Previews モードで編集 (音を何処かに必ず入れること) -> 書き出し

で、できた成果物を iTunes Connect にアップロードします
動画撮影はシミュレータを使っています
他のデバイスサイズに関しては iTunes Connect の設定で使いまわすことができるので使いまわします
iMovie で動画を書き出す際には 30 秒以内の動画になっていることを確認してください

最後に

App Previews とスクリーンショットを取得するコツを紹介しました
現時点ではこの方法でやることでアプリに必要な情報を作成することができると思います

2017年8月25日金曜日

Xcode のシミュレータで動画を撮影する方法

概要

App Previews 用の動画を撮影するのに iPhone6+ 以上の解像度を持つ端末が必要になることは多々あります
自分は iPhone6 しか持っていなくどうやって 1920 x 1080 の動画を撮影しようか悩んでいました
そんなときシミュレータで動画を撮影する方法を知ったので紹介します

環境

  • macOS X 10.12.6
  • Xcode 8.3.3 (8E3004b)

撮影方法

  1. Xcode から iPhone7 (or iPhone6+) シミュレータを起動しアプリを起動します
  2. ターミナルで以下のコマンドを実行します
  3. xcrun simctl io booted recordVideo appvideo1.mov
  4. シミュレータを操作して動画を撮影します
  5. 完了したら Ctrl+c で撮影を終了します

すると appvideo1.mov というファイルが作成されており、これが 1920 x 1080 の解像度の動画を撮影することができます
一点注意点としてはシミュレータのサイズは 100% でなければなりません
これを 25% などにして撮影すると動画のサイズが 1920 x 1080 ではなくなってしまいます

最後に

これを覚えておくと App Previews の動画撮影がかなり楽になります
もちろん実機があるならばそれを使ったほうが絶対に良いと思います
実機がない場合にはかなり便利な技だと思います

参考サイト