ゴマちゃんフロンティアV2へようこそ!

本ブログはゲーム統合開発環境「Unity」を使用したゲーム開発の日記です!
ときどきイラストを描いたり、写真を交えた日記も書いています!

【開発環境】

OS Windows7 SP1 64bit
CPU Intel(R) Core(TM) i7-4770 CPU @ 3.40GHz
メモリ 12GB
グラボ GeForce GTX 645
使用ソフト Microsoft Visual Studio(C#)
Blender
GIMP

旧ブログの記事について

本ブログは2015年9月にFC2ブログから移転しております。
過去記事につきまして、「開発方針が変わっている」「Unityの仕様が現状と異なる」等、そのままでは不適切と判断しました。
なので、知識・技術面で重要そうな記事のみをピックアップし、再編集して新ブログに追加している段階です。
Googleのインデックスの関係上、検索内容とブログ内容に差異が発生する可能性がありますので、ご了承ください。
また「前のブログのこの記事読みたい!」というものがありましたら、ご連絡頂ければ追加致します!

お問い合わせフォームはこちら

【Unity】シーン上のオブジェクト取得時にシングルトンっぽい動きの実現

というわけで、時々「ブログ名変なの」とか言われるりべるんです。
「ゴマちゃん」で「フロンティア」で「バージョン2」ってだけなのですが・・・。

今回もよく分からないタイトルですが、要は「シーン上に1つしかないオブジェクトのコンポーネント」を取得する際に、シングルトンパターンっぽく出来ないかというお話です!

「Unityでシングルトン」的な話題はぐぐると幾らでもヒットしますが、MonoBehaviour を継承していないクラスでのお話だったりします。
(MonoBehaviour を継承しなければ new 演算子でインスタンス化できます)
しかし、Unity 的にはシーン上に管理用オブジェクトを配置し、インスペクターから必要なパラメータやプレハブの設定をしたい・・・という場面があります。

自分のゲームで言うと、ゲーム内の共通エフェクトをまとめた EffectManager 何かが正にそれです。
エフェクト毎にプレハブを設定しておき、マネージャから生成・破棄の管理をしています。

shot2ss20161203194259608

そんなシーン上に1つしかないオブジェクトを効率よく取得する方法について考えてみました!

単一オブジェクトのコンポーネント取得

シーン上に存在するオブジェクトを取得するには Find() か何かしてくるわけですが、そのオブジェクトを複数のクラスから参照する場合、いちいち Find() → GetComponent() は避けたいところです。
ということで、「自身の静的フィールドにインスタンスを保持し、あったらそれを返す、なかったら Find()&GetComponent() する」という動きを実装してあげます。

using UnityEngine;
using System.Collections;

public class TestManager : MonoBehaviour {

    private static TestManager testManager;

    public void hoge() {
        Debug.Log("hoge");
    }

    public static TestManager getInstance() {
        if (testManager == null) {
            testManager = GameObject.Find("TestManager").GetComponent<TestManager>();
        }

        return testManager;
    }
}

取得時はシンプルに getIntance() を呼ぶだけです。

void Start() {
    TestManager testManager = TestManager.getInstance();
    testManager.hoge();
}

GameObjectで応用

別にクラスのインスタンスに限らず、シーン上に1つしか存在しない GameObject を取得する場合にも使えます。
ちょっと変えるだけでいけそうな雰囲気。

using UnityEngine;
using System.Collections;

public class TestManager : MonoBehaviour {

    private static GameObject testManager;

    public void hoge() {
        Debug.Log("hoge");
    }

    public static GameObject getInstance() {
        if (testManager == null) {
            testManager = GameObject.Find("TestManager");
        }

        return testManager;
    }
}

取得方法も変わりません。
GameObject に対して操作するときはこれでもOKですね。

GameObject testManager = TestManager.getInstance();
Debug.Log(testManager.ToString());

シーン上に1つしかないもの・・・DirectionalLight や mainCamera とかですね。
特にカメラはあれこれ動かす機会が多くなりそうなので重宝します。
実現のために1つスクリプトを作成しなければなりませんが、複数クラスから Find() を連発するよりはマシかと思われます。

まとめ

そんなわけで、MonoBehaviour を継承したオブジェクトを何度も取得する場合について考えてみました!
Unity5 で早くなったとはいえ、Find() や GetComponent() を繰り返すのは避けたいところですね。

河口湖紅葉祭り(2016)に行ってきました!

というわけで、最近やたらと多い「行ってきました」シリーズです。
秋といえば紅葉!ということで、「河口湖紅葉祭り」に行ってきました!

cimg2443_r

去年も来ているので2年連続になります。
前は11月3日に行ったので、今年は2週間ほど遅れて行くことになりました。
3日ではまだ見頃とは言い難い感じであり、今年は色付きが遅いらしいので、時期的にはよろしい感じです。

去年の様子は↓の記事をご参照下さい。

河口湖紅葉祭り(2015年)に行ってきました!

もみじ回廊

去年は夜行ったのでライトアップされておりましたが、今年は朝方に来ました。

家を出たときは曇でしたが、河口湖まで行くと綺麗に晴れていました。
が、近隣の専用駐車場は凄まじく混雑しており、9時半には全て満車になってしまっていました。
また、誘導員がかなり適当な誘導をするため、満車のところをぐるぐる回っている車ばかりでした。

仕方ないので猿回し劇場の駐車場に2時間500円で停めることに。
それでもかなり埋まっていたので、紅葉シーズンに行く場合は覚悟した方がよさそうです。

ということで、以下ひたすら紅葉です!

cimg2448_r

cimg2393_r

cimg2407_r

cimg2438_r

cimg2435_r

cimg2417_r

cimg2436_r

人が多いため、家族揃った写真を綺麗に撮るのは厳しいです。
その手のサービスをやっている業者がいたので、抵抗がなければ利用するのが確実です。
大きい写真で出力したものは1000円かかりますが、自分のカメラを渡せば撮ってくれます。

ほうとう不動

山梨県民なのでほうとうにはときめかないのですが、本番のしっかりしたほうとうを食べたことがなかったので、昼に家族で行ってみました。
もみじ回廊の駐車場からは700mほどです。

cimg2453_r

山梨といえばほうとうという風潮がありますが、うどんを更に太くしたような麺なので人を選ぶ食感な気がします。
かぼちゃを溶かして食べると非常に馴染んで美味しくなるのでオススメです。

まとめ

そんなわけで、今年も河口湖紅葉祭りに行ってきました!
秋は寒すぎず暑すぎず、景色も綺麗とお出かけにはもってこいですね。
これから冬にかけて出歩くのもきつくなってくるので、今のうちに秋を堪能しておくべきでしょうか。

【Windows10】電源オプションが正常に表示されない現象と格闘した件について

というわけで、今回は自分のPCでちょっとしたトラブルがあったので、そのお話になります。
いつの間にか電源オプションの項目が正常に表示されなくなり、電源プランの変更や作成も「お使いの電源プランの情報が利用できません」と言われてしまいます。

shot2ss20161119215938247

shot2ss20161119215958327

shot2ss20161119220006969

結論から言うと、レジストリの値がおかしかったのが原因だったようです。
「powrprof.dll」というファイルを見に行っているようですが、そのファイルパスが変になっていました。

以下、自分が試したことを書いていきます!
参考になれば幸いです。

※レジストリを変更した場合、最悪OSが起動できなくなってしまいます。
一切の責任は負いかねますので、変更する際は自己責任で!

試したこと

ぐぐるとよく出るのは、コマンドプロンプトから電源プランの設定を初期化する方法。
以下のコマンドでいけるみたいです。

powercfg -restoredefaultschemes

自分の場合はこれでは解決しませんでした。
powercfg /l で電源プランの一覧を表示すると、下のように普通に表示されるので、完全に逝っているわけではなさそうでした。

既存の電源設定 (* アクティブ)
———————————–
電源設定の GUID: 381b4222-f694-41f0-9685-ff5bb260df2e *
電源設定の GUID: 8c5e7fda-e8bf-4a96-9a85-a6e23a8c635c
電源設定の GUID: a1841308-3541-4fab-bc81-f71556f20b4a

お次はサービス「Power」の確認。
これが無効になっていたりすると表示されないみたいです。

・・・が、

shot2ss20161119224820063

元気に(?)動いておりました!
ぐぐった感じでは、電源プランのメッセージ「RCPサーバーを利用できません」といった旨が表示されている場合はこの可能性が高いかもです。

レジストリ修正

以下のサイトで (Windows7ですが) 似たような事例が紹介されておりました。

http://jutememo.blogspot.jp/2013/01/windows-7-cpu.html

ということでレジストリを疑ってみることに。
弄る前に復元ポイントは必ず作成しておきましょう。

自分の場合、以下のレジストリ直下のキーに原因がありました。
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\PowerSchemes

ここに2つの値が保存されており、powrprof.dll へのファイルパスが登録されているわけですが・・・

shot2ss20161119225936089

空白部分は自分のユーザ名なので消してありますが、Users\Music\Windows~というなかなかぶっ飛んだパスになっておりました。
これを現在 powrprof.dll があるパスに修正したところ、上手く表示されるようになりました!
カスタムプランも問題なく作れます。

shot2ss20161119231901938

思えば Windows7→10 にアップグレードしたとき、幾つかのプログラムが「Music」フォルダ直下に移動したという珍現象が起きましたが、それの影響なのかもしれません。
また、プラン作成時に「指定されたファイルが見つかりません」と言われたので、それも怪しかったです。
何故アップグレード時に Music に移動したのかは永遠の謎ですが・・・。

しかしこれで終わりではなく、この記事を書いている過程で上で紹介した「電源プランの初期化」コマンドを実行したところ、また Music 下を参照するようになってしまいました。
どうやらデフォルト設定のキーも逝っているため、初期化では解決しなかったようです。
ということで、以下のレジストリ直下のキーも修正しました。

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Power\User\Default\PowerSchemes

まとめ

そんなわけで、電源オプションが表示されない現象を頑張って調べてみました。
元々は高速スタートアップを無効化しようと始めた作業ですが、レジストリまで弄る厄介な作業になってしまいました。
ともかく正常に直ってよかったです。

重ねてになりますが、壊れても責任は負いかねますので、試す場合は自己責任でお願いします!

【Unity】StateMachineBehaviourからアニメーションの特定タイミングで処理を実行する方法

というわけで、ひさびさにUnity関係のお話です!
今回はタイトルにもある「StateMachineBehaviour」を使ったスクリプトのお話です。

shot2ss20161116195302218

「StateMachineBehaviour」を継承したクラスは、AnimatorController のステートに設定することができます。
主に「ステートに遷移した直後」「ステート中」「他のステートへ遷移する直前」のタイミングで関数が呼び出されますが、「ある特定のタイミングで関数を呼び出す」ということが出来ません。

アニメーションをインポートした際に ImportSettings の Events から出来るようですが、指定する関数名を直打ちするのがあまり好きではありません。
SendMessage() もそうですが、エディタのリファクタリング機能で置換できないので、後々変更があった場合に面倒なことになってしまいます。
アクセス修飾子が private であってもぶち抜いて実行されるのもどうかと思います。

特定タイミングで処理を実行するスクリプトの作成

冒頭で「Events をつかいたくない!」と言ったものの、現状の StateMachineBehaviour に「アニメーションの特定タイミングで処理を実行する」的な機能はありません。
なので自作するわけですが、ステート中に実行される OnStateUpdate() と 、AnimatorStateInfo.normalizedTime を組み合わせればいけそうです。

そんなわけで作ったスクリプトがこちら!

using UnityEngine;

namespace StateController.PlayerAnimator {

    public class ExecuteTest : StateMachineBehaviour {

        [SerializeField]
        protected float execute_time;

        private bool execute_flg;

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            execute_flg = false;
        }

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (execute_time < stateInfo.normalizedTime && !execute_flg) {
                execute_flg = true;

                // 何らかの処理
                Debug.Log("hogehoge");
            }
        }

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            execute_flg = false;
        }
    }
}

ポイントは OnStateEnter() と OnStateExit() 内で execute_flg をfalseに設定している部分です。
これがないと execute_flg が true から切り替わらず、2回目以降if文の中が実行されません。
Enter で行えば Exit では要らない気もしますが、念のため!

またif文の条件ですが、normalizedTime が f loat で細かく変化する関係上、「execute_time == stateInfo.normalizedTime」では不可能に近いです。
四捨五入することも考えましたが、近い値になると複数回通ってしまうためNG。

shot2ss20161116202600048

これを AnimatorController 上のステートに設定し、execute_time を設定すればOKです。
AnimatorStateInfo.normalizedTime はアニメーション実行時間に応じて0~1の値を取るので、execute_time も0~1で指定する必要があります。

ただし、ループするステートの場合は上手くいきません。
ループするステートの場合は normalizedTime が1でリセットされず、どんどん加算されていきます。

ということで、normalizedTime 小数点以下を取得して判定するように修正します。
直したのは OnStateUpdate() のif文判定だけです。

using UnityEngine;

namespace StateController.PlayerAnimator {

    public class ExecuteTest : StateMachineBehaviour {

        [SerializeField]
        protected float execute_time;

        private bool execute_flg;

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            execute_flg = false;
        }

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (execute_time < stateInfo.normalizedTime % 1 && !execute_flg) {
                execute_flg = true;

                // 何らかの処理
                Debug.Log("hogehoge");
            }
        }

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            execute_flg = false;
        }
    }
}

「剰余演算子」なるものを使えば簡単にできるみたいです。
ちなみに normalizedTime の整数部分がループ回数に相応するようです。
覚えておくと役に立つ・・・かも。

使用例

自分が使っている一例として、「特定タイミングで iTween.MoveBy() を実行しキャラクターを移動させる」があります。
上で載せたスクリプトを少し修正します。

using UnityEngine;

namespace StateController.PlayerAnimator {

    public class MoveCharacter : StateMachineBehaviour {

        [SerializeField]
        protected float execute_time;

        [SerializeField]
        protected Vector3 velocity;

        [SerializeField]
        protected float move_time;

        private bool execute_flg;

        public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            execute_flg = false;
        }

        public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            if (execute_time < stateInfo.normalizedTime % 1 && !execute_flg) {
                execute_flg = true;

                iTween.Stop(animator.gameObject);
                iTween.MoveBy(animator.gameObject, iTween.Hash(
                    "amount", velocity,
                    "time", move_time
                ));
            }
        }

        public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) {
            execute_flg = false;
        }
    }
}

shot2ss20161116202724703

インスペクター上で実行するタイミングと移動ベクトル、移動時間を設定します。
アニメーションの途中で「1歩踏み込んで攻撃」的なモーションがあっても安心です。

20161116_01

実際に動かすと上のような感じ。
分かりにくいですが、縦斬り→薙ぎ払いまで1モーションで、execute_time は0.5に設定してあります。

別に iTween でなくても良いので、animator.gameObject.getComponent() とかと組み合わせればもっといろいろ出来ます。
StateMachineBehaviour のスクリプト内に処理をあれこれ書きたくない方は、素直に Player クラスあたりを getComponent() して処理させるのも有りだと思います。

まとめ

そんなわけで、StateMachineBehaviour 上から特定タイミングで処理を実行する方法について考えてみました!
特定タイミングであれこれしたい場面は多いので、活用する機会は多くなりそうです。

【Blender】boneが逆回転してしまう現象の私的な調査メモ

というわけで、前に続き Blender に関するお話です!
今回はアニメーション設定時に bone が逆回転してしまう現象について調べてみました!

特に縦横にキャラクターを回転させるアクションを作った際に起こりがちです。
自分のように頭身の低いキャラを使っている場合はモロに響く問題です。

どうやら「Quaternion」が関係してくる話のようです。
何とかならないか調べてみたので、その内容について書いておきます!

※書いている人が数学全然ダメなので、問題ないという保証はありません。
試す際は自己責任で!

逆回転するフレームを修正したフレームで登録する

逆回転する部分を手動で補完し登録することで、「内部的に逆回転しているが描画されない」ようになります。
力技っぽいアプローチですが、Quaternion の回転制限を受けずに修正できます。

shot2ss20161112234715154

実際にやってみるとなかなか大変です。
「10フレームかけて1回転する」アニメーションを直す場合でも、逆回転する度にポーズを修正してキーフレームを登録する必要がある上、手動なので動きもぎこちなくなってしまいます。
とは言え、Quaternion にした状態で180度以上回転させるにはこれしかないようです。

boneのRotationModeを変える

そもそもこの現象は、Quaternion が -1~1 の間しか表現できないため、その最短の補完を行う際に起こるみたいです。
角度でいうと -180~180 ですね。
これ以上に回転を掛けようとしなければ発生しないようですが、キャラクターの回転アクションを180度以内で収めるのは無理があります。

このあたりのお話は Unity の公式マニュアルに書いてあります。
https://docs.unity3d.com/ja/current/Manual/QuaternionAndEulerRotationsInUnity.html
https://docs.unity3d.com/ja/current/Manual/AnimationEulerCurveImport.html

shot2ss20161112235006561

で、Blender の bone はデフォルトの回転モードが Quaternion になっているそうです。
これを Euler に切り替えればOKです。

テスト用に作ったウサギちゃんで試してみます。

20161113_02

20161113_01

上は回転モードが Quaternion、下は Euler で、X軸に220度ほど回転させた際の動きです。
Quaternion では逆回転して戻ってしまいますが、Euler ではしっかり1回転してくれます。

shot2ss20161112235208705

この状態で Unity にインポートした場合、Unity 側で自動的に Quaternion のキーフレームを考慮したモーションに修正してくれるようです。
リサンプリングした結果が厳密ではない場合、「Resample Curves」のチェックを外すと Euler のキーフレームをそのまま使えるようです。

ただし、Euler を使うと「ジンバルロック」という問題が発生するようです。
3方向の特定の軸が重なった場合に2方向にしか自由に動かせなくなる・・・的なものらしいですが、自分もよく分かりませんorz
ぐぐった感じでは、1~2軸のみ動かす場合は Euler でも問題ないようです。
Blender の場合は bone ごとに回転モードを選択できるため、逆回転しうるものだけ Euler にするのも有りかもしれません。

shot2ss20161112235339520

また、回転モードを変えた bone はキーフレームを設定し直す必要があります。
(Rotation だけ黄色くなっていない=キーフレームが設定されていない)
モーション作成中に変えると面倒なので、アニメーション系の作業に取り掛かる前に決めておくのがよさそうです。

まとめ

そんなわけで、Blender で bone が逆回転する現象について調べてみました!
かなり難しい話題なので、また何か分かったら考えてみます。