全身3Dスキャンで"フリー素材"化した自分とARで共演してみた話

前回の記事「全身3Dスキャンで"フリー素材"化して、踊ったりしてみた話」で、自分自身をアニメーション付きの3Dモデルデータにしたところまで書きました。その後ARKit 3を使ってARコンテンツ化したので、だいぶ時間が経ってしまいましたが記事にしてみます。

完成したものはこんな感じ。

ソースコードはこちら。

https://github.com/miyabi/dancing-ar

やりたいこと

ARKitで3Dオブジェクトを描画するには、SceneKit、SpriteKit、iOS 13から追加されたRealityKitを使うか、あるいはMetalを使って独自に描画します。今回は手軽にARコンテンツを作成するために、SceneKitを使用します。

また、画面のタップした位置に3Dモデルを配置する仕様にします。リアルな3DモデルなのでARで表示するときもリアルに表示したいと思い、以下の実装もおこないます。

  • 3Dモデルに当たる照明と実際の照明をなじませる(Light estimation)
  • 影を描画する
  • 3Dモデルで手前の人物が隠れてしまうのを防ぐ(People occlusion)

準備

mixamoでアニメーションを付けた3DモデルはFBX(.fbx)かCollada(.dae)形式でダウンロードできます。.dae形式は特別なライブラリが不要で、Xcodeがビルド時に自動的にSceneKitで扱える形式に変換してくれるので、この形式でダウンロードしておきます。

SceneKitで扱える3Dモデルのフォーマット/アニメーションつき3DモデルをSceneKitで使う

XcodeのNewメニュー→Projectから「Augmented Reality App」を選択し、Content Technologyを「SceneKit」にします。

以下、コードはすべてViewController.swiftに実装しています。

モデルの配置

.dae形式の3Dモデル(とテクスチャー)をリソースとしてプロジェクトに追加します。viewDidLoadで3Dモデルを読み込みますが、あとで画面のタップした位置に配置したいのでシーンには追加せずにSceneKitのノードとして保持しておきます。

var baseNode: SCNNode!

override func viewDidLoad() {
    // ...

    baseNode = SCNScene(named: "Samba Dancing.dae")!.rootNode.childNode(withName: "Base", recursively: true)
}

ARWorldTrackingConfiguration.planeDetectionに.horizontalを指定して、平面を検出できるようにします。

override func viewWillAppear(_ animated: Bool) {
    // ...

    configuration.planeDetection = [.horizontal]

    // ...
}

Main.storyboardにTap Gesture Recognizerを追加し、画面のタップを受け取るアクションを追加します。

タップした位置に対応する現実空間の座標を取得するにはARFrame.hitTestを使用しますが、今回はiOS 13から追加されたARSCNView.raycastQueryとARSession.raycastを使って実装します。

@IBAction func screenDidTap(_ sender: UITapGestureRecognizer) {
    guard let view = sender.view else { return }
    
    if sender.state == .ended {
        let location = sender.location(in: view)
        guard let raycastQuery = sceneView.raycastQuery(from: location, allowing: .estimatedPlane, alignment: .horizontal) else { return }
        guard let raycastResult = sceneView.session.raycast(raycastQuery).first else { return }

        let position = SCNVector3Make(
            raycastResult.worldTransform.columns.3.x,
            raycastResult.worldTransform.columns.3.y,
            raycastResult.worldTransform.columns.3.z
        )

        let newBaseNode = baseNode.clone()
        newBaseNode.position = position
        sceneView.scene.rootNode.addChildNode(newBaseNode)
    }
}

まずraycastQueryにSCNView上での座標、raycastをヒットさせる対象とそのalignment(今回は検出された水平面にするのでそれぞれ.estimatedPlaneと.horizontal)を指定してクエリを作ります。そのクエリをARSession.raycastに渡すことによって、現実空間の座標を取得できます。ただしsimd_float4x4なので、これをSCNVector3に変換します。

保持しておいた3Dモデルのノードをcloneして、得られた座標を設定し、シーンに追加します。

Light estimation

Light estimationはキャプチャしたシーンの画像から照明を推定する機能で、初期のARKitから利用可能です。

まず、Light estimationを反映させるための環境光を追加します。

var ambientLightNode: SCNNode!

override func viewDidLoad() {
    // ...

    let ambientLight = SCNLight()
    ambientLight.type = .ambient
    ambientLight.shadowMode = .deferred
    ambientLightNode = SCNNode()
    ambientLightNode.light = ambientLight
    scene.rootNode.addChildNode(ambientLightNode)
}

ARWorldTrackingConfigurationのisLightEstimationEnabledとenvironmentTexturingを有効にします。

override func viewWillAppear(_ animated: Bool) {
    // ...

    configuration.isLightEstimationEnabled = true
    configuration.environmentTexturing = .automatic

    // ...
}

ARSCNViewを使用している場合、これだけで現実空間の照明の変化は自動的にシーンの照明に反映されます。

影の描画

単に3Dモデルを表示するだけでは"浮いて"見えてしまいます。そこで影を描画します。

まず、光源となるDirectional lightを追加します。この照明で影を描画するのでcastsShadowをtrueにします。また、レンダリングパスの最後に描画するのでshadowModeを.defferedに、shadowColorは半透明の黒にします。

少し影をぼかしてよりリアルにするため、shadowSampleCountとshadowRadiusの値を8に設定します。

最後に、Light estimationでは光源の方向を推定できないため、真上から光が当たるようにして、シーンに追加します。

var directionalLightNode: SCNNode!

override func viewDidLoad() {
    // ...

    let directionalLight = SCNLight()
    directionalLight.type = .directional
    directionalLight.intensity = 1000
    directionalLight.castsShadow = true
    directionalLight.shadowMode = .deferred
    directionalLight.shadowColor = UIColor.black.withAlphaComponent(0.5)
    directionalLight.shadowSampleCount = 8
    directionalLight.shadowRadius = 8
    directionalLightNode = SCNNode()
    directionalLightNode.light = directionalLight
    directionalLightNode.rotation = SCNVector4Make(1.0, 0.0, 0.0, -Float.pi / 2.0)
    scene.rootNode.addChildNode(directionalLightNode)
}

このままでは影が投影される平面がシーン上にないので、検出した現実空間の平面のジオメトリから作ります。このジオメトリのマテリアルのcolorBufferWriteMaskを空にしておくことで、透明で影だけ投影できる平面が作れます。

func renderer(_ renderer: SCNSceneRenderer, didUpdate node: SCNNode, for anchor: ARAnchor) {
    guard let planeAnchor = anchor as? ARPlaneAnchor else { return }
    
    let device = MTLCreateSystemDefaultDevice()!
    let planeGeometry = ARSCNPlaneGeometry(device: device)!
    planeGeometry.update(from: planeAnchor.geometry)
    planeGeometry.firstMaterial?.colorBufferWriteMask = []
    node.geometry = planeGeometry
}

People occlusion

通常ARコンテンツにおける3Dモデルは、現実空間をキャプチャした画像の上に描画されるため、手前にある現実の物体の奥に3Dモデルを表示することができず、違和感の元となっていました。

iOS 13から使用可能になったPeople occlusionによって、手前の人物をマスクすることができるようになり、この違和感を低減させることができます。

Occluding Virtual Content with People | Apple Developer Documentation

ただし、この機能にはA12 Bionic以降に搭載されている8コアのニューラルエンジンが必要なため、すべてのデバイスで使用することはできません。対応しているデバイスは、XR以降のiPhoneと、第3世代以降の12.9インチまたは11インチのiPad Pro、第3世代以降のiPad Air、第5世代以降のiPad miniとなっています。

People occlusionを有効にするには、ARWorldTrackingConfiguration.supportsFrameSemanticsで対応しているデバイスか確認したあと、ARWorldTrackingConfiguration.frameSemanticsに.personSegmentationWithDepthを設定します。

override func viewWillAppear(_ animated: Bool) {
    // ...

    if ARWorldTrackingConfiguration.supportsFrameSemantics(.personSegmentationWithDepth) {
        configuration.frameSemantics.insert(.personSegmentationWithDepth)
    }

    // ...
}

ARSCNViewを使用している場合は、これで3Dモデルより手前の人物がマスクされます。

ARCoachingOverlayView

最後に本筋ではありませんがiOS 13で追加されたARCoachingOverlayViewを実装しておきます。

ARCoachingOverlayView - ARKit | Apple Developer Documentation

バイスに現実空間を認識させるためには、その仕組み上、デバイスを少し動かすなど一定の手順があります。これまでは開発者自身がそれをユーザーに伝える工夫をする必要がありましたが、それがOSの標準機能で可能になりました。

実装はこんな感じです。sessionに使用するARSession、goalに.horizontalPlaneを設定して(今回は水平面の検出なので)、ARSCNViewに追加します。

var coachingOverlayView: ARCoachingOverlayView!

override func viewDidLoad() {
    // ...

    coachingOverlayView = ARCoachingOverlayView()
    coachingOverlayView.session = sceneView.session
    coachingOverlayView.delegate = self
    coachingOverlayView.activatesAutomatically = true
    coachingOverlayView.goal = .horizontalPlane
    
    sceneView.addSubview(coachingOverlayView)

    coachingOverlayView.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        coachingOverlayView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
        coachingOverlayView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
        coachingOverlayView.widthAnchor.constraint(equalTo: view.widthAnchor),
        coachingOverlayView.heightAnchor.constraint(equalTo: view.heightAnchor),
    ])
}

最後に

実際のプロジェクトでは、スケールを設定するスライダーとPeople occlusionの有効/無効を切り替えるスイッチを実装しています。

スケールを小さくしてたくさん3Dモデルを配置するとわらわらして楽しい感じになります。

スイッチの方は、People occlusionを有効にすると(ARWorldTrackingConfiguration.frameSemanticsに.personSegmentationWithDepthを設定すると)、なぜか平面に影が描画されないという現象があったので実装しました。
3Dモデル上には影が投影されているので、colorBufferWriteMaskあたりの問題なのかなあとは思っています。

こちらからは以上です。

全身3Dスキャンで"フリー素材"化して、踊ったりしてみた話

先日9月25・26日に開催されたUnite Tokyo 2019のブース展示で、Digital Artisanさんが全身3Dスキャナー「3D GATEWAY」を展示、3Dスキャン体験をおこなっていたので、スキャンしてもらってきました(3Dスキャン体験は他のイベントなどでも実施されているようです)。

スキャンしたデータはSketchfab上で公開され、FBXかglTFでダウンロードが可能です。

sketchfab.com

ポリゴン数は20万。

f:id:mybdesign:20191002151235p:plain

せっかくなので動かしてみたいと思い、Adobemixamoでリギングしてみました。mixamoはAdobeのアカウントで使用できます。

元のFBXをそのままmixamonにアップロードしたらエラーになってしまったので、ポリゴンを減らしてみます。

f:id:mybdesign:20191002151722p:plain

Cheetah 3DのTools→Polygon→Simplifyでは微妙な感じだったので、一旦OBJに変換してMeshmixerを使うことにしました。

f:id:mybdesign:20191002151855p:plain

Selectから全部選択してEdit→Reduce、デフォルトで50%になっているのでそのままAccept、再びOBJでエクスポートします。

これをマテリアル、テクスチャーと一式zipにしてmixamoにアップロードしてみましたが、真っ黒になってしまいました。

f:id:mybdesign:20191002152055p:plain

Cheetah 3Dで開いてみると余計なマテリアルがあったので、これを削除して再びzipにしてアップロードすると、今度はうまくいきました。

f:id:mybdesign:20191002152306p:plain

mixamoでは手首、肘など8箇所をマークするだけで、自動的にリギングしてくれます。とっても簡単!

f:id:mybdesign:20191002152630p:plain

リギングしたモデルには、2,000以上あるアニメーションを割り当ててその場でプレビューできます。これで色々試しているだけでもだいぶ楽しい!

アニメーションを割り当てたモデルはFBXなどでダウンロードできます。もちろんUnityのアセットとして使用できますが、マテリアルがない状態になるので割り当て直す必要があります。

f:id:mybdesign:20191002170929p:plain

こちらからは以上です。

iPhone XR/XS (Max) のeSIMを使うと海外でのデータ通信が超絶便利だった話

とてつもなく久しぶりに記事を書きます。

海外でデータ通信を利用する際、国内通信事業者のデータローミングプランやレンタルのWi-Fiルーターでは割高だったり通信量に不満があるので、僕はよく現地通信事業者のプリペイドプランを使っていました。

現地のプリペイドプランは割安なのがとても魅力的です。よく使っていたアメリカのAT&Tでも8GBが$40、昨年ポーランドに行ったときにはOrangeの6GBのLTE通信プランが5ズウォティ(日本円にして約150円ほど)で利用できました。

f:id:mybdesign:20190529173854j:plain:h320

しかし、現地のプリペイドプランを利用するには、到着後にSIMを入手しなければならなかったり、事前にSIMを入手していても空港などWi-Fiの繋がるところでアクティベートする必要があります。何をするにもデータ通信に依存している身としては、勝手の分からない海外で一刻も早くライフラインを獲得したいのに、なかなか繋がらないと焦りと不安を感じてしまいます…。

今回、iPhone XR/XS (Max) に搭載されたeSIMが超絶便利と聞き、ちょうど海外に行く機会もあったので早速使ってみました!

eSIMとは

iPhone XR、XSとXS Maxには物理的なnano-SIMのほかに電子的に書き換え可能なeSIMが内蔵されています。これらのSIMは、それぞれ別の回線として使用することができます。

support.apple.com

また、多くの通信事業者がeSIMに対応したモバイル通信プランを提供しています。各通信事業者はこれらのプランを利用するためのアプリをApp Storeで提供しているため、面倒な手続きをすることなく利用できます。

support.apple.com

先日ハワイへ家族旅行した際は、このAppleが公開しているeSIMサービス提供事業者リストにもあるT-Mobileを利用しました。T-Mobileは、以前Apple SIMが内蔵された9.7インチのiPad Proで利用したことがありましたが、そのときはアクティベーションを設定アプリからおこない、また支払いに必要なリフィルカードを入手するのに手間取った記憶があります。今回はT-Mobileが提供している専用アプリがあり、日本のクレジットカードでも決済ができたので非常にスムーズでした。

5/30 追記:

T-Mobile eSIM

T-Mobile eSIM

eSIMを使ってみる

そして今回、韓国への出張で再びeSIMを利用してみたので、その手順をまとめてみます。

上記のeSIMサービス提供事業者リストには韓国の事業者が入っていません。しかしグローバルなサービスプロバイダーのGigSkyが韓国にも対応しているので、こちらを利用することにします。
利用を開始するための設定は出発前、日本国内にいるうちにできます。まずはApp StoreからGigSkyのアプリをインストールします。

GigSky Global Mobile Data

GigSky Global Mobile Data

  • GigSky, Inc.
  • 旅行
  • 無料

アプリを起動したらまず目的地を選択します。

f:id:mybdesign:20190529174747p:plain:w240

続いてデータ通信プランを選択します。韓国向けには5種類のプランが提供されていました。今回は3泊4日の出張だったので2GB/¥3,600のプランを選択します。

f:id:mybdesign:20190529175421p:plain:w240

プランを選択するとログイン画面になるので、必要があればアカウントを作成してログインします。

f:id:mybdesign:20190529175516p:plain:w240

続いて支払い。Apple Payにも対応しているのでとてつもなく簡単です。

f:id:mybdesign:20190529175542p:plain:w240

あとは手順に従ってポチポチとボタンを押していくだけでモバイル通信プランが追加されます。他と区別するための回線名だけは自動的に設定されないので、手順に従ってコピーしておいたラベルを貼り付けます。

f:id:mybdesign:20190529175609p:plain

これで利用できる状態になりました。アンテナピクトが二重に表示されているのが確認できます(片方がいつも使っている主回線で、もう片方がGigSky)。

f:id:mybdesign:20190529175819p:plain:w240

まだ日本国内にいるので、モバイルデータ通信/デフォルトの音声回線とも主回線にしておきます。
現地に到着したらモバイルデータ通信をGigSkyに切り替えます。

f:id:mybdesign:20190529180045p:plain:w240

これでもう現地回線につながります!簡単すぎて怖いレベル。飛行機が着陸後、モバイル機器が利用になってすぐ利用できるので、入国に時間がかかってもその間にホテルまでの道のりを調べたり、現地のレストランを検索するなどの余裕ができます。

ちなみに今回はSNSへの写真のアップロードやGoogle Mapsを主に利用して、トータル1.5GBほどの通信量でした。

f:id:mybdesign:20190529180141p:plain:w240

いかがでしょうか?みなさんも海外に行く機会があるときはぜひ使ってみてください!

UnityでGoogle Play Game Servicesとmobile backendを導入してハマった話

UnityでGoogle Play Game Servicesを導入済みのプロジェクトにmobile backendを導入した際、Androidビルドでハマったので解決に至るまでをメモとして残します。

環境

  • Unity 5.3.4f1

序章

Google Play Game Services

Google Play Game Services(以下GPGS)のプラグインは、以下のリポジトリから現時点での最新版0.9.32をプロジェクトに導入済みでした。

playgameservices/play-games-plugin-for-unity: Google Play Games plugin for Unity

mobile backendのインポート

プッシュ通知に対応するため、NIFTY Cloudのmobile backendのUnity SDK(2.1.1)を導入しました。

NIFTYCloud-mbaas/ncmb_unity: ニフティクラウド mobile backend Unity SDK

このとき、android-support-v4.jarとgoogle-play-services.jar(と、res/values/version.xmlも)はGPGSのプラグインによりプロジェクトにインポート済みだったので、外しました。

起動、そしてクラッシュ

この時点でビルドしてAndroidの実機上で起動してみると、クラッシュします。OMG!

Android Studioのlogcatを見ると、以下のようなログが出ています。

04-10 00:48:34.984 24899-24988/? E/AndroidRuntime: FATAL EXCEPTION: IntentService[RegIntentService]

Process: com.mybdesign.mobilebackendexample, PID: 24899

java.lang.Error: FATAL EXCEPTION [IntentService[RegIntentService]]

Unity version : 5.3.4f1

Device model : LGE Nexus 4

Device fingerprint: google/occam/mako:5.1.1/LMY48T/2237560:user/release-keys

04-10 00:48:34.989 567-1033/? W/ActivityManager: Force finishing activity 1 com.mybdesign.mobilebackendexample/com.nifty.cloud.mb.ncmbgcmplugin.UnityPlayerNativeActivity

どうやらmobile backendのプッシュ通知のデバイス登録をおこなうタイミングでクラッシュしているようでした。

GPGSのダウングレード

経験上、GPGSのインポート、セットアップで追加されるandroid-support-v4とgoogle-play-servicesがほかの広告SDKと競合を起こすことがよくあったので、まずこれを疑ってGPGSのダウングレードを試みました。

プロジェクトにインポート済みのGPGSを一旦削除して(GooglePlayGamesフォルダ、PlayServicesResolverフォルダ、Plugins/Androidのlibsフォルダ、MainLibProjフォルダをまず削除、Unityを再起動してからplay-services-.aarとsupport-.aarを削除)、old-builds/GooglePlayGamesPlugin-0.9.21.unitypackageをインポート、Windowメニュー→「Google Play Games」→「Setup」→「Android setup...」から再度セットアップをしました。

するとConsoleに大量のエラーが出力されます。

Assets/GooglePlayGames/ISocialPlatform/PlayGamesPlatform.cs(94,21): error CS0104: `Logger' is an ambiguous reference between `UnityEngine.Logger' and `GooglePlayGames.OurUtils.Logger'

Unity 5.3以降でUnityEngine.Loggerが定義されたので、こいつが曖昧だということですね。

ここは面倒ですが、GPGSでGooglePlayGames.OurUtils.Loggerクラスを使っているファイル全部に以下を追加して解決してやります。

using Logger = GooglePlayGames.OurUtils.Logger;

これで再度ビルドして実機で実行してみます。

04-10 01:29:21.040 28391-28411/? I/Unity: OnRegistrationSucceeded

(Filename: ./artifacts/generated/common/runtime/UnityEngineDebugBindings.gen.cpp Line: 37)

今度はmobile backendのデバイス登録に成功しているようですが...。

今度はGPGSが...

logcatをよく見ると、以下のようなログが出ています。

04-10 01:29:16.432 28391-28411/? D/GamesUnitySDK: Performing Android initialization of the GPG SDK

04-10 01:29:16.447 28391-28411/? E/GamesNativeSDK: Can't register class com/google/android/gms/appstate/AppStateManager: an exception occurred.

04-10 01:29:16.515 28391-28411/? E/GamesNativeSDK: Could not register one or more required Java classes.

04-10 01:29:16.688 28391-28411/? I/Unity: InvalidOperationException: There was an error creating a GameServices object. Check for log errors from GamesNativeSDK

at GooglePlayGames.Native.PInvoke.GameServicesBuilder.Build (GooglePlayGames.Native.PInvoke.PlatformConfiguration configRef) [0x00000] in <filename unknown>:0

at GooglePlayGames.Native.NativeClient.InitializeGameServices () [0x00000] in <filename unknown>:0

at GooglePlayGames.Native.NativeClient.Authenticate (System.Action`1 callback, Boolean silent) [0x00000] in <filename unknown>:0

at GooglePlayGames.PlayGamesPlatform.Authenticate (System.Action`1 callback, Boolean silent) [0x00000] in <filename unknown>:0

at GooglePlayGames.PlayGamesPlatform.Authenticate (System.Action`1 callback) [0x00000] in <filename unknown>:0

at GooglePlayGames.PlayGamesLocalUser.Authenticate (System.Action`1 callback) [0x00000] in <filename unknown>:0

at MobileBackendExample.Start () [0x00000] in <filename unknown>:0

(Filename: Line: -1)

どうやらGPGSが機能していないようです。そういえばGPGSのログイン画面が出なかったような...。

エラーの内容を見てみると、AppStateManagerクラスが登録できない様子。ググってみると、あるバージョンのgoogle-play-servicesからAppStateManagerクラスがなくなっているらしいことがわかりました。

Error Creating a GameServices object. ? Issue #884 ? playgameservices/play-games-plugin-for-unity

とりあえず旧バージョンのgoogle-play-servicesが入手できないか探してみます。するとStack Overflowにまんま直球の質問が。

java - How to download older google play services? - Stack Overflow

ここからひとつずつダウンロードしてAppStateManagerクラスが含まれているバージョンのgoogle-play-servicesを探します。

jarの中身を確認するのにはJava Decompilerを使いました。GUIもあって便利です。

r26のgoogle-play-servicesにはAppStateManagerクラスがあったので、これをプロジェクトにインポート済みのものと差し替えます1

Finally it works!

再度ビルドして動作を確認すると、GPGSのログイン画面も表示され実績やリーダーボードも機能していました。mobile backendの方も、正常にプッシュ通知が機能していました。

ライブラリのソースコードを書き換えたり、古いバージョンを使ったりしているので正直おすすめはできませんが、同様にハマった人の参考になればと思います。

オマケ

実はあと2つほどハマったポイントがあります。

  • Player SettingsのStripping LevelをDisabledにしておかないとmobile backendのデバイス登録が機能しなかった。
  • 使っているAndroidプラグインが多すぎて、ビルド時にToo many method referencesが発生→不要なjarを削除。


  1. ここで再度GPGSのセットアップをおこなうと、google-play-servicesを元に戻されてしまうので注意。 ?



from mybdesignの投稿 - Qiita

書評をいただきました! - 『uGUIではじめるUnity UIデザインの教科書』

Unity UIについて解説した書籍『uGUIではじめるUnity UIデザインの教科書』が世に出て3週間あまり、色々な方から書評をいただきましたのでご紹介したいと思います。

【書評】uGUIではじめるUnity UIデザインの教科書 - hotmiyacchiの休日

AppBank Gamesの社長でもあった宮川さん(@hotmiyacchi)による書評です。僕自身開発に関わったタイトル「ダンジョンズ&ゴルフ」や「トキノラビリンス」ではお世話になりました!相変わらずアツい文章で推していただいています。

ご本人も書かれていますが、共に仕事をしたのがUnity 3.5〜4.5時代なので、まだuGUIを使用して共に仕事をできていないのが残念ですね〜。

uGUIの使い方が分かる本「uGUIではじめるUnity UIデザインの教科書」 - 卵は世界である

続いて、派手髪開発者・ブロガーの@akio0911さんによる書評です。本書の各章について的確にご紹介いただいています。

普段はiOSアプリを開発されていたり、各地でiOSアプリの開発講座を開催されていたりする@akio0911さんならではの視点で書かれています。

Unity初心者は3時間でどこまでGUIを作れるのか? -- Unity UIデザインの教科書 - ここぽんのーと

そして最後に、@cocoponさんによる書評です。そろそろUnityを使ってみたいというオーラを出していたので、無理矢理本書をお渡ししました。

まったくのUnity初心者が、本書を読んで3時間でどこまでできるかということを実践していただいています。さすが!

この調子でどんどんUnityの世界にはまっていってもらいたいです。

お三方には書評をいただき、ありがとうございました!

『uGUIではじめるUnity UIデザインの教科書』のご購入を考えている方はぜひ、ご参考にしてみてください。

uGUIではじめるUnity UIデザインの教科書-Unity5対応

ポップの掲示をお願いしに書店へ行ってきました。 - 『uGUIではじめるUnity UIデザインの教科書』

『uGUIではじめるUnity UIデザインの教科書』が発売され多くの書店にも並んだということで、表紙のアイロンビーズを制作・撮影してくれたヌンちゃんとポップを作り、去る9月3日都内の書店さんに掲示をお願いしに行ってきました。

pop2.jpg

この日、都内の10の書店さんに伺いました(※版元さんを通じて事前に書店さんの了承を得た上で伺っています):

紀伊國屋書店 新宿南店様、紀伊國屋書店 新宿本店様、ジュンク堂書店 池袋本店様、ジュンク堂書店 吉祥寺店様、有隣堂 ヨドバシAKIBA店様、書泉ブックタワー様、八重洲ブックセンター 本店様、丸善 丸の内本店様、啓文堂書店 渋谷店様、ジュンク堂書店 渋谷店

朝から蒸し暑く1店目からいきなり汗だくになっていましたが、どの書店さんにも快く対応していただきました。ありがとうございました!

ポップは僕がすべて手書きしたものと、ヌンちゃんに書いてもらったものに署名したものの2種類があります(さらにキャッチコピーも2種類あります)。

もしかすると東京から遠く離れた地にもあるかもしれません。ぜひ探してみてください。

Amazonでのご購入はこちら:

【Amazon.co.jp限定】uGUIではじめるUnity UIデザインの教科書-Unity5対応 特典PDF付き

Amazonでの早期購入でAppendixを再編集した特典PDFがダウンロードできます。

特典PDFのお申し込みはこちら(2015年9月14日(月)23:59まで):

https://book.mynavi.jp/blog/pc/2015/08/12/811/

『uGUIではじめるUnity UIデザインの教科書』が発売されました!

IMG_0798.JPG

『uGUIではじめるUnity UIデザインの教科書』が発売され、書店にも並び始めました。著書が書店で平積みされているのを見ると、また感慨もひとしおです(感慨の余り写真がぶれている...)。

最近Unity関連の書籍がいくつか出版されたこともあり、Unityコーナーのできている書店も多く、ますますUnityの人気ぶりを知ることとなりました。

本書では主にUnityの新UIシステムについて取り扱っていますが、これからUnityを始めようとしている開発者の方向けのチュートリアルも収録していますので、ぜひご活用ください!

Amazonでのご購入はこちら:

【Amazon.co.jp限定】uGUIではじめるUnity UIデザインの教科書-Unity5対応 特典PDF付き

Amazonでの早期購入でAppendixを再編集した特典PDFがダウンロードできます。

特典PDFのお申し込みはこちら(2015年9月14日(月)23:59まで):

https://book.mynavi.jp/blog/pc/2015/08/12/811/