いつの間にかGalaxy s8+も使えるようになったので触ってみる。

UnityのExamples - HelloARのシーンを覗いてみた

あとから気付いたけど上記参考文献にも書いてあった。
オブジェクトが何をしているのかをまずチェック。

  • ARCore Device
    • ARCoreの管理オブジェクト。子オブジェクトにカメラもついてる。
  • Environmental Light
    • カメラキャプチャを元にカラコレを弄っているっぽい。
  • Point Cloud
    • フレーム中の特徴点を検出するみたい。多分アプリ中に出てる青いドットのことだと思う。
  • Example Controller
    • アプリ固有コード部分。
    • First Person Camera
      • タッチ時におけるレイキャスト判定で参照してる
    • Tracked Plane Prefab
      • 検知した平面のビジュアライズ機能をサポートしてるだけっぽい。デバッグ用途かな。
    • Andy Android Prefab
      • タップ時に発生するモデル。あれってアンディっていうのか…
      • 実行中に生成される際はAnchorというオブジェクトの子として置かれる模様
    • Searching For Plane UI
      • 平面検索中に表示するUI。

GoogleのHelloAR Sample App Tourページ覗いとく

  • モーショントラッキングで平面検知できる
  • 見っけた平面のビジュアライズオブジェクトは都度作られる
  • まだ1つも見つけてない場合は検索中のUI表示する、見つけたら消える
  • タップされるとレイキャストで特徴点か平面タップしたかチェック
    • タップしてたらTrackableHit.Poseでモデル置く
      • PoseはAnchorを作るために使う
      • Anchorはモデルを現実世界の同位置に留める役割を持つ
      • Anchorが不要になったらGameObject.Destroy()すること

負荷を減らす方法

仕事でUnity-モバイル案件を受けることになって負荷検証をしようとしてるのだけど、そもそもどういうことをすればいいのかわからないのであれこれ試してみる。

<負荷を知る方法>
■そもそもの問題点
Android端末で実機の処理時間をUnity-Profilerで調べるとCPU時間しか出ない。
GPU時間はどーすりゃええねんって感じ。

■試したこと
・Adreno Profilerを導入した
Snapdragonだったら大体これ使うっていうの見つけたので導入。
でもフレーム単位の処理時間を一発でわかるような項目見当たらず…

FPSGPU-% Busyって項目から割り出せばいいのかな…?
なんか違うような気がして仕方ない。

●PCで試してみる
実際モバイル端末でも有用になるかわからないけど、参考としてPCではどのような対処が有用なのかを試してみる。
リトライしやすいのもあるし。


こんなシーンで試してみる。
初期SDユニティちゃんについてるスクリプトは一通り消してプレハブ化した。
乗ってるスクリプトはカメラにアタッチしてある特定のプレハブをインスタンス化するスクリプトのみ。
400体が映るような画を出すと、CPU:57ms前後、GPU:50ms前後となる。
なお、マテリアルは各々インスタンス化(Instancedと表示される)状態にしています。

■CPU
・fbxのRig-Optimize Game Objectを有効にする(CPU:57ms -> 45ms程度に改善)
 →ノードがヒエラルキーから消え去るので、動的に特定の骨を動かす等ができなくなってしまう…
・Lightコンポーネントを無効にする(CPU:57ms -> 42ms程度、GPU:20ms程度に改善)
 →当然真っ暗になるので実用的ではない…
・Light - Shadow typeをSoft ShadowからNo Shadowにする(CPU:57ms -> 42ms程度、GPU:20ms程度に改善)
 →無効にした時とほぼ同等。影描画のコストのデカさが覗える。
GPU
・Cast Shadowsを一律Off (GPU:50ms -> 35ms程度に改善)
・Player - Other Settings - GPU SkinningをON(GPU:50ms -> 37ms程度に改善)
・Quality - Shadows - Shadow ProjectionをClose Fitに変更する(GPU:50ms -> 44ms程度に改善)
 →稼げるが、制約に留意する必要がある(参考:http://tsubakit1.hateblo.jp/entry/2016/11/25/005559
・Quality - Shadows - Shadow Disatnceを150->10に変更する(GPU:50ms -> 36ms程度に改善)
 →短すぎると見た目が問題になるかも。

結果はいかに…!

結論から行くとダメダメのダメな感じでした。。。

ダメに関してはダメとして今後の糧にするため記録に残しておきます。

最終的な進捗

動画に収めてみました。動画では録音ができていないのですがSEも最低限ついてはいます。


前回投稿時からの変更点としては…

といったところでしょうか。

反省点

  • 風邪を引いた、ウイルス性胃腸炎に罹った
    • ここ数年、風邪に罹りやすくなっては来てた自覚はあったんですがここでウイルス性胃腸炎までかかるとは想定外でした…さすがにあれは堪えた…さすがにこんなん毎年続くともう辛みしかないので来年は体調管理について考えます。ここで最高にモチベーションが下がってしまったのはある。
  • 計画性無さすぎ
    • ここは正直ノリと勢いで始まったのでしゃーないところはあるものの、カレンダー書くこと考えたら一日から作業始めてたら記事書く時間によって作業時間が消えるのでもっと前からやろうね…
  • ゲームとしての調整をする時間が無かった
    • そもそも完結してないので何言ってんだというのもあるけど、全然調整してないので伏せまくったりするんですよね。球筋のブレも入れてないので直接狙ってくるばかりだし…

課題点

  • 被弾時の演出についていいアイデアが思いつかなかった
    • 今のVignetteも正直目に悪い印象があるのと、今一つピンと来ないところがあります。とは言えもろにゲージを出すのは避けたかったんですよね…結局ユニティちゃんには出しちゃってるけど…ゲージ系は避けたうえでステータスがわかるような表現を探したいです。
  • キャラクターに複数の動きを求める
    • 本当はスライディングして移動とかもしてみたかったんですが、現状でもいろいろとやることが増えたりしてしまったので時間食いが激しい傾向がありました。ここいらをうまく組むことも短期開発には重要そうです。
  • アセットストアで提供されているアセットだけでは限界がある
    • 当然っちゃ当然ですが、モデルやモーション、パーティクル、SEはなかなか望んでいるものが見つかりにくいです。システム系やエディタ拡張系のアセットを嗜む程度に、前述のものに関しては自前で工面していく必要がありそうです。アセットストアで完結させる場合はある程度事前に下調べの上、ゲームルールを考えないと途中で見つからず頓挫してしまいモチベーションが下がります。モデルは百歩譲るとしても、モーションは自炊できるようにしておくとそれだけで幅が広がりそうな印象です。
  • 掬うってすごく怠くない?
    • 根幹を揺さぶる課題点ですが、基本は掬わないといけないのでしゃがむプレイを要求されます。初期から地面の高さが比較的高めに置いてあったのは座って確認できるようにするためだったりします。狙ってはいたところではあるのですが、長引くとその動作に対するダルさが増していくので最悪掬わず、コントローラワンボタンで発生するというのもありなのかもしれません。


本当はまだいろいろと反省点や課題点はあるのですが、ローカルな話も多々あるので割愛します。
ユニティちゃんのリアクションが単調とはいえ、雪玉を掬って投げてリアクションが起きるというのは大変良さがあるので、皆さんも試してみてください。

今後は?

せっかく作ったのでユニティちゃん…ではないかもですが、何かしら公開できる形にしたさはあります。
内部システム系のコードは使いまわせるように整理したうえで有効活用していく予定です。
アドベントカレンダーは何かやろうとする動機付けとしては有効だったと思います。
来年はひとりごにょごにょをするかわかりませんが、何かしらのカレンダーに参加してみたいなぁ…

進捗ダメです…

※この記事は『ひとりVRゲーム作ろうな Advent Calendar 2016』の18日目の記事です。

先週の月曜の夜から所謂ウイルス性の胃腸炎にかかりまして、普通に食事を摂れるレベルには回復したのですが、まだ本調子には遠いような感じです。
ぶっちゃけ手洗いうがいはしてこの様なので避けようあんのか怪しいですが、皆さんもお気を付けください。。。

さて、進捗のほうですが、単刀直入にダメダメです。

進捗内容

  • ざっくり障害物置いた
  • HPゲージ的なものを追加
  • アニメーションをスクリプト側で制御する形に変更
    • AnimatorStateの遷移が増えてきて鬱陶しく感じてしまったため…
  • ユニティちゃんの行動をステート化
    • しゃがみ
    • 負け倒れる

現状は以下の動画のような状態。
あと一週間…果たして終わるのか!?(終わらせるとは言ってない)

球を投げられる

※この記事は『ひとりVRゲーム作ろうな Advent Calendar 2016』の11日目の記事です。

二日休んだばかりかカレンダーを途切れさせてしまったせいか風邪を患う始末です…
続けようとする意志が大事ということで続けます。

ユニティちゃんが戦えるようにする

前回(7日目)の状態だけではただただ棒立ちで雪を受けるばかりだったので戦えるように雪球を投げられるようにしてあげます。
モーションを作る思考はなかったので早速アセットストアで探すも…

アレ、意外と見当たらない…?

無いというところは実は想定してませんでした。投げモーションくらいあるやろ的な…
地道にアニメーションカテゴリをさまよってたところ以下のアセットを見つけました。
Meelee Axe Pack


明らかにボールを投げてる感じではないのですが、
振りかぶっているようなモーションがあったのでこれをありがたく使わせてもらうことにしました。
(無くて進捗ないですは避けたかったのもあります。。。)

とりあえずユニティちゃんに雪玉を顔面めがけて狙ってもらうようにしました。
以前から使っているThrowableObjectを利用して顔面に当てる斜方投射の計算をスクリプト上で行い、仰角を拾ったうえでRigidbodyのvelocityに初速を渡します。
非物理環境でチェックできていないのであれですが、狙い目が少し下になっているので計算ミスってるかもというところもあるので、スクリプトは非公開で…

投げることばかり考えてたせいか、そもそも雪をすくうという行為が必要なことが頭からずっぽ抜けてました。
アニメーションは…やはりパッと見つからなそうだったので、同パック内の手を背の後ろに隠すモーションのときにしれっと雪を握るようにしました。
これらをアニメーションステートに登録。


これがこうなります。


主だった変更はこんな感じ。
そろそろ前後の履歴が必要になってきそうな気がしたのでローカルでgitを使うようにしました。

これでとりあえず攻撃できるようになりました。
この状態で投げ合ってるだけでも楽しいのですが、ゲームにならんぞおい!って感じなのでちょっとルールを実装していきます。
こっちの顔面のあたり判定もないしね…

ユニティちゃんに雪玉をぶつける

※この記事は『ひとりVRゲーム作ろうな Advent Calendar 2016』の7日目の記事です。

ね、寝るまでが1日だから…(震え声)

今回はユニティちゃんに雪玉をぶつけるとやられモーションを取ってもらいます。

自分が衝突していると知覚させる

今回ユニティちゃんにアタッチしているColliderは複数のGameObjectにそれぞれアタッチされています。


ルートとなっているGameObjectに対して自分が当たっていることを通知する必要があります。
ということで各種コリジョンイベント通知クラスとそれを受信するクラスを作成しました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// 子のイベント送信者.
/// </summary>
public class CollisionEventSender : MonoBehaviour {

    private CollisionEventReceiver _sendTo = null;

    public static CollisionEventSender Instantiate(
        GameObject attachTo, CollisionEventReceiver sendTo)
    {
        Debug.Assert(null != attachTo);
        Debug.Assert(null != sendTo);

        CollisionEventSender sender = attachTo.AddComponent<CollisionEventSender>();
        if (null != sender)
        {
            sender._sendTo = sendTo;
        }
        return sender;
    }

    /// <summary>
    /// デフォルトコンストラクタ.
    /// インスタンス化の際にSender-Receive間の紐づけを行いたいので使用禁止.
    /// </summary>
    private CollisionEventSender() { }

    void OnTriggerEnter(Collider other)
    {
        _sendTo.ReceivedOnTriggerEnter(other);
    }
    void OnTriggerStay(Collider other)
    {
        _sendTo.ReceivedOnTriggerStay(other);
    }
    void OnTriggerExit(Collider other)
    {
        _sendTo.ReceivedOnTriggerExit(other);
    }

    void OnCollisionEnter(Collision other)
    {
        _sendTo.ReceivedOnCollisionEnter(other);
    }
    void OnCollisionStay(Collision other)
    {
        _sendTo.ReceivedOnCollisionStay(other);
    }
    void OnCollisionExit(Collision other)
    {
        _sendTo.ReceivedOnCollisionExit(other);
    }
}


受信側は以下の通り。
いちいち手動でアタッチさせていくのも面倒かな、という気がしたので、
コンストラクタで子階層でColliderのつく子にはCollisionEventSenderを自動でアタッチするようにしてみました。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public delegate void ColliderEvent(Collider other);
public delegate void CollisionEvent(Collision other);

/// <summary>
/// 子の衝突イベント受信者.
/// </summary>
public class CollisionEventReceiver
{
    public ColliderEvent OnTriggerEnterCallback { private get; set; }
    public ColliderEvent OnTriggerStayCallback { private get; set; }
    public ColliderEvent OnTriggerExitCallback { private get; set; }
    public CollisionEvent OnCollisionEnterCallback { private get; set; }
    public CollisionEvent OnCollisionStayCallback { private get; set; }
    public CollisionEvent OnCollisionExitCallback { private get; set; }


    private List<CollisionEventSender> _senders = null;

    private CollisionEventReceiver() { }
    public CollisionEventReceiver(Transform parent)
    {
        _senders = new List<CollisionEventSender>();
        AddSenderFromChildren(parent);
    }

    /// <summary>
    /// 通知者達の破棄.
    /// デストラクタでコールするとメインスレッド以外でGameObjectはDestroy出来ない様なエラーが発生するため、
    /// 受信者側で明示的に破棄する必要があります.
    /// </summary>
    public void Dispose()
    {
        for (int i = 0; i < _senders.Count; ++i)
        {
            GameObject.Destroy(_senders[i]);
        }
    }

    public void ReceivedOnTriggerEnter(Collider other)
    {
        if (null != OnTriggerEnterCallback)
        {
            OnTriggerEnterCallback(other);
        }
    }
    public void ReceivedOnTriggerStay(Collider other)
    {
        if (null != OnTriggerStayCallback)
        {
            OnTriggerStayCallback(other);
        }
    }
    public void ReceivedOnTriggerExit(Collider other)
    {
        if (null != OnTriggerExitCallback)
        {
            OnTriggerExitCallback(other);
        }
    }

    public void ReceivedOnCollisionEnter(Collision other)
    {
        if (null != OnCollisionEnterCallback)
        {
            OnCollisionEnterCallback(other);
        }
    }
    public void ReceivedOnCollisionStay(Collision other)
    {
        if (null != OnCollisionStayCallback)
        {
            OnCollisionStayCallback(other);
        }
    }
    public void ReceivedOnCollisionExit(Collision other)
    {
        if (null != OnCollisionExitCallback)
        {
            OnCollisionExitCallback(other);
        }
    }

    /// <summary>
    /// 子供に送信用コンポーネントを追加.
    /// Colliderがついているオブジェクトが対象となります.
    /// </summary>
    /// <param name="parent">親となるトランスフォーム</param>
    private void AddSenderFromChildren(Transform parent)
    {
        Debug.Assert(null != parent);

        Collider[] targets = parent.GetComponentsInChildren<Collider>();
        for (int i = 0; i < targets.Length; ++i)
        {
            CollisionEventSender sender = targets[i].GetComponent<CollisionEventSender>();
            if (null == sender)
            {
                sender = CollisionEventSender.Instantiate(targets[i].gameObject, this);
            }
            _senders.Add(sender);
        }
    }
}


実はこれ作ってるときにハマったのですが、デストラクタでGameObject.Destroy()を呼ぶと、

Destroy can only be called from the main thread.

というエラーが表示されてうまくいかない様です。
そもそもデストラクタを呼ぶのが間違い、のような気がしてきたので取り急ぎは明示的に破棄をコールする形にしました。

Receiverを使う場合は以下のような感じでデリゲートアクセサに設定してください。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

/// <summary>
/// キャラのリアクション.
/// </summary>
public class Reaction : MonoBehaviour {

    private CollisionEventReceiver _receiver = null;
    private Animator _animator = null;

    void Awake()
    {
        _receiver = new CollisionEventReceiver(transform);
        if (null != _receiver)
        {
            _receiver.OnTriggerEnterCallback = null;
            _receiver.OnTriggerStayCallback = null;
            _receiver.OnTriggerExitCallback = null;
            _receiver.OnCollisionEnterCallback = OnHitEvent;
            _receiver.OnCollisionStayCallback = null;
            _receiver.OnCollisionExitCallback = null;
        }
        Debug.Assert(null != _receiver);
    }

    // Use this for initialization
    void Start() {
        _animator = GetComponent<Animator>();

        Debug.Assert(null != _animator);
    }
    
    void OnDestroy()
    {
        if (null != _receiver)
        {
            _receiver.Dispose();
        }
    }

    private void OnHitEvent(Collision other)
    {
        // やられアニメーション遷移用トリガ
        _animator.SetTrigger("onDamage");
    }
}



Receiverをルートにアタッチすると実行時にはこのようにスクリプトがアタッチされます。
楽ちん。

と、ここでトラブル。
コリジョンはあるのにリアクションが起きなかったり、地面突き抜けたり、雪玉壊れのパーティクル発生イベントが起きないといった問題が…
眠い頭では収集つけられそうになかったのでとりあえず以下のようにRigidbodyを追加して、IsKinematicで物理演算をOffにすることで対処。
後ほどこの捻じれ設定は何とかしないとダメそうです。


とまあ、そんな感じでごまかした結果がこちら。


ユニティちゃんかわかわのかわーって感じです!

キャラを配置する

※この記事は『ひとりVRゲーム作ろうな Advent Calendar 2016』の6日目の記事です。

想定していた以上に残業が多すぎて若干辛みが…
わずかな進捗でもとりあえず書くことに意義があると信じて続けます。
今日はほんとちょっとだけしか触ってません…

ユニティちゃんにご登場いただく

ということで満を持してとりあえずユニティちゃんに登場いただきます。
http://unity-chan.comのダウンロードページは1.2なのでアセットストアのやつはもしかして古い?



古いせいなのか不明だけどシーンビューではスク水が前面に現れる!!!
ほかのプロジェクトだとうまく出てるんですけどね…


出しているだけでかわいい!

モーションしてもコライダがくっつくように各骨に沿ってなんとなくコライダを設定します。


結果こんな感じ。間近じゃなければ、意外とアバウトでもそんなにわからないかも。


今回はこれだけです…次あたりとりあえず被弾のけぞりでも入れますか。