Apple Engine

Apple, iPhone, iOS, その周辺のことについて

ARKit や SceneKit で電光掲示板的な横に流れる文字を表示する

f:id:x67x6fx74x6f:20190307171630g:plain
横に流れる文字(SceneKit)

AR 空間で文字数の多いテキストを表示する場合、 紙のように板ポリゴン一面や空間にするケースがあると思われる。

AR は現実空間と密接になっているため、サイズが小さいと近くに寄る必要があり、逆に大きいと全体を見る為に遠ざかる必要がある。

初代 Microsoft HoloLens のようにウインドウとして表示し、エアタップからスクロールして見る方法もあるが操作が面倒ではある。

以上の2つのように、身体的に動作が伴う場合、反復する状況だと操作が面倒になる。

 

回避策としては、音声でガイダンスをしたり、動画として伝えるべきことを表示したりすることもできるし、 今回のように電光掲示板のような横に流れる自動で流れる文字を採用するのも良いだろう。

 

横に流れる文字 iOS のネイティブ環境で実現させるには以下の3つの方法が考えられる。

  • SpriteKit などから動画のテクスチャを表示する
  • 文字のテクスチャ画像を作成して、UV 座標の移動をアニメーションさせる
  • SpriteKit でラベル(SKLabelNode)をアニメーションさせる

 

以下ではこの3点について実装していきたいと思う。

 

SpriteKit で動画のテクスチャを表示する

実装的にはもっとも簡単。
動画のテクスチャに関しては過去の記事や下のサンプルファイルを参考にしてもらいたい。

動画の場合文字の表示スピードや背景の演出など様々表現ができる上に、アプリ制作する際にアニメーション部分もデザイナ側に任せるため、この部分の作業の切り分けができる。

デメリットは、iPhone や SpriteKit で Apple Pro Res のデータが扱えないため、動画ファイルだけでは背景を透過することができない。
(SpriteKit でマスクをかけることができるがかなり処理が重くなる)
また、再生をループする場合、再生場所を最初に戻す処理があるため、滑らかにループされることが期待できない。

 

UV 座標の移動で実現する

実装的にはかなり簡単で、使用する画像が少なければ他の2つに比べて一番負荷が少なくなる可能性が高い。
特に同じ文字のループを表示する場合、デフォルトのテクスチャ設定がループになっているので UV 座標の移動を繰り返すだけで簡単にループし続け、かつ処理が軽い。
(下のサンプルファイルを参照)

任意の文字を表示したい場合ではなく固定の文字を画像として表示したい場合はもっとも有効である。
(UIKit など OS のフレームワークから画像として文字を作成すれば事前に画像をつくるれば可能だが……)

 

以下の様に SCNAction で U 座標に移動するカスタムアクションを設定するとこの関数を呼ぶだけでアニメーションされるので便利。

extension SCNAction {

    open class func moveDiffuseU(_ duration:TimeInterval = 1) -> SCNAction{
        
        var uvPostion = float4x4.init(1)
        
        return SCNAction.customAction(duration: duration, action: { (node, elapsedTime) in
            uvPostion[3].x = Float(elapsedTime) / Float(duration)
            node.geometry?.firstMaterial?.diffuse.contentsTransform = SCNMatrix4(uvPostion)
        })
    }
}

 

SpriteKit でラベル(SKLabelNode)をアニメーションさせる

SpriteKit で動画を表示させるようにラベル(SKLabelNode)を配置しアニメーションさせる。
SKScene にノードを貼り付けるため、テキストフィールドなどから任意の文字を表示しアニメーションすることができる。

デメリットとしては動画や画像と異なり文字装飾が NSAttributedString ぐらいとなる。
一応、SKEffectNode から CIFilter でフィルター処理をしたり、シェーダーを書くことで装飾することはできるが。

 

少しだけコードの解説

// シーンの設定
skScene = SKScene(size: CGSize.init(width: 1024, height: 256))
skScene.anchorPoint = CGPoint(x:0.5, y: 0.5)

// カメラ設定
let cameraNode = SKCameraNode()

cameraNode.position = CGPoint(x: 0, y: 0)
cameraNode.yScale = -1

skScene.addChild(cameraNode) // addChild しないとカメラが適応されない
skScene.camera = cameraNode
// アニメーションさせる SKLabelNode の設定
func setScrollText(_ text:String){
    
    // フォント設定(fontSize と labelPosY は雑に設定している)
    let systemFont = UIFont.systemFont(ofSize: 140, weight: UIFont.Weight.bold)
    let fontSize:CGFloat = 140
    let labelPosY = -CGFloat(fontSize / 3)
    
    // ラベル設定
    let labelNode = SKLabelNode()
    labelNode.fontName = systemFont.fontName
    labelNode.text = text
    labelNode.fontSize = fontSize
    labelNode.horizontalAlignmentMode = .left
    labelNode.fontColor = SKColor.white
    labelNode.position = CGPoint(x: skScene.size.width / 2, y: labelPosY) // シーンのアンカーが中央なので幅を2で割ったものを X 軸とする。
    
    skScene.addChild(labelNode)
    
    // アニメーション設定
    let totalTextSize = labelNode.frame.width + skScene.size.width // シーンの幅とラベルの大きさ = 移動距離
    
    let duration = (totalTextSize / labelNode.frame.height) * 0.4 // 移動距離をラベルの高さで割る。早すぎるので 0.4 かけて遅くする。0.4 の値を増やせば遅く、少なくすると速くなる
    let endPos   = CGPoint(x: -totalTextSize, y: labelPosY) // ラベル移動アニメーションの最終位置
    
    // SKAction で移動を設定し、移動が完了したらラベルを消す。
    let move = SKAction.move(to: endPos, duration: TimeInterval(duration))
    labelNode.run(move, completion: {() -> Void in
        labelNode.removeFromParent()
    })
}

 

サンプルコード

ARBoardSK は文字入力確定か画面タップで文字が横に流れる。
ARBoardSK2 の解説はまた後ほど。

github.com

 

まとめ

ARKit や SceneKit で電光掲示板ような横に流れる文字の実装のやり方がわかったと思う。

ケースバイケースなので、状況にあった方法で設定していくとよいのではというところ。