20210110のUnityに関する記事は8件です。

【Unity初心者必見】Toggle(Group)のOnValueChangedでハマった話

初めに

ToggleのOn/Offの切り替え時にメソッドを呼び出したいとき、OnValueChangedにメソッドをAddListenerすることが多いと思う。
これと同じように、複数のToggleをToggleGroupで管理してOn/Offの切り替え時にメソッドを呼び出そうとした時、微妙な挙動の違いでメソッドが複数回呼び出されてハマったのでメモしておく。

環境

Unity:2019.1.5f1

起こったこと

設定は以下のスクショの通り。
Toggle3つ(A,B,C)をToggleGroupで管理し、どれか一つのみにチェックが入るようになっている。
スクリーンショット 2021-01-10 22.43.38.png

ToggleGroupには以下のコードをアタッチ

ToggleController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;

public class ToggleController : MonoBehaviour
{
    public void Change()
    {
        Toggle activeToggle = this.gameObject.GetComponent<ToggleGroup>().ActiveToggles().First();
        Debug.Log(activeToggle.name);
    }
}

各ToggleのOnValueChangedにChangeメソッドをインスペクタからアタッチする。
すると、次のような挙動をする。
画面収録-2021-01-10-23.05.17.gif
メソッドが2回呼び出されているようだ。
これは、例えばB→AとToggleを切り替える時
BがOffになる時のOnValueChangedとAがOnになる時のOnValueChangedが両方動いてしまうことが原因である。
つまりOnValueChangedはチェックマーク(isOn)がついた時ではなくToggleの状態が変わった時に発火するようだ。

対処法

コードを次のように修正した。
なお、特に大差はないがスクリプトからメソッドをAddListenerした。

ToggleController.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using System.Linq;

public class ToggleController : MonoBehaviour
{
    public ToggleGroup toggleGroup;

    private void Start()
    {
        var toggles = toggleGroup.gameObject.GetComponentsInChildren<Transform>();
        foreach (var toggle in toggles)
        {
            if (toggle.GetComponent<Toggle>() != null)
            {
                toggle.GetComponent<Toggle>().onValueChanged.AddListener(Change);
            }
        }
    }

    public void Change(bool state)
    {
        if (state)
        {
            Toggle activeToggle = this.gameObject.GetComponent<ToggleGroup>().ActiveToggles().First();
            Debug.Log(activeToggle.name);
        }
    }
}

変更点はChangeメソッドに引数を持たせたことである。
実際はこのメソッドは2回呼び出されているのだが、引数のstateでToggleにチェックが入っているかどうかを判断しているため、中の処理が行われるのは1回になる。

すると次のような挙動になる。
画面収録-2021-01-10-22.54.51.gif

まとめ

  • ToggleGroupのToggleのOnValueChangedを使うときは、処理が重複しないよう注意が必要
  • やりたい処理に応じて2つの挙動を使い分けることが大事
  • Toggleにはあまり複雑な処理を持たせない方が良い気もする
  • 頑張ればButtonでもできる

最後に

ここまで読んでいただきありがとうございました。
投稿者はまだまだUnity初心者のため、アドバイスやコメント等あればお待ちしております。
この記事が何かのお役に立てれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】LWRPプロジェクトで作成したShaderGraphをUnity2019.3以降の通常3Dプロジェクトに持ってきたらピンクになる

Unity2019.3以降で3Dテンプレートを使って制作していて途中からLWRPで作成したShaderGraphを持って来ようとしたときに詰まった。絶対忘れるので備忘録として書きます。結論としてはShaderGraphから生成したコードをコピペしたシェーダーを使ったら正常に表示されました。

Unity2020.1.9

現象

Unity2019.3以降では描画パイプラインがLightweight Render Pipeline(LWRP)からUniversal Render Pipeline(URP)に換わった。
プロジェクト作成時にテンプレートでUniversal Render Pipelineを選択すればLWRPで作成したShaderGraphでも問題無く持って来ることが出来るが(勝手に色々変換してくれる)、3Dなど別のテンプレートを選択した場合、後からパイプラインをUniversalRenderPipelineに設定しても正常に表示されない。

[Unity2018.2.21]
image.png
[Unity2020.1.9]
image.png

やったこと

1.pipelineをUniversalRenderPipelineに設定する

そもそもpipelineをUniversalRenderPipelineに設定していないと、ShaderGraph上で「The active Master Node is compatible with the current Render Pipeline, ...」というエラーが出ます。
image.png
Package ManagerからUniversal RPをインストールし、ProjectビューからRendering>Universal Render Pipeline>Pipeline Asset(Forward Renderer)を選択。
image.png
プロジェクト内にUniversalRenderPipelineAssetが出来るので、Project Settings>Graphics>Scriptable Render Pipeline Settingsに設定する。
image.png

2.新しくShaderを作成する

1.の手順でShaderGraphのエラーが消えたのでいけると思ったがマテリアルはピンクのまま。
マテリアルを選択してEdit>Render Pipeline>Universal Render Pipeline>Upgrade Selectee Materials to UniversalRP Materialsを実行してもダメ。
↓の手順で生成されたコードを見てもLightweight -> Universalの変換がきちんと行われていたので原因が分からなかったが、コードをコピペして新たに作成したシェーダーを使ったら正常に表示された。
image.png
もっと良い解決策を知ってる方は教えてください。

参考リンク

https://gametukurikata.com/basic/projecttemplate
[↓LightweightからUniversalにupgradeする際の書き換えなど]
https://docs.unity3d.com/Packages/com.unity.render-pipelines.universal@10.2/manual/upgrade-lwrp-to-urp.html
[↓LWRPをloadできないときは]
https://baba-s.hatenablog.com/entry/2019/04/21/230500
[↓Packageの場所]
http://tsubakit1.hateblo.jp/entry/2018/08/16/221016

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AR空間に豆腐を召喚するにあたってハマったポイント

ARアプリを作りたいと思い以下記事を参考にサンプルアプリを作ったのですが
UnityにもARにも慣れていなかったためいくつかハマったポイントがあったので書きます。
https://qiita.com/shun-shun123/items/1aa646049474d0e244be

表示されたエラーメッセージを残しておけばよかったのですが、忘れてました…。

Unityでビルドできない

Project Settings > Player > Others で Target minimum iOS Version を 「12」に変更、
Architecture を「ARM64」に変更することで解消しました。
Screen Shot 2021-01-10 at 16.55.45.png

XCodeでビルドできない

以下を参考に開発用ビルド証明書を作成、再びUnityでXCodeプロジェクトをビルドすることで
XCodeでビルドできるようになりました。
https://nekonenene.hatenablog.com/entry/unity-build-to-ios-for-free

iOSにインストールできない

以下を参考にXCodeとiOSの対応バージョンを合わせることで解消しました。
https://developer.apple.com/jp/support/xcode/

インストールしたアプリが正常に作動しない

Project Settings > Player > XR Plug-in Management で
Plug-in Providersの「ARKit」にチェックを入れることで解消しました。
Screen Shot 2021-01-10 at 19.20.09.png

できあがったもの

ソースコードは以下です。
https://github.com/nerikara/ar-tofu

参考

AR Foundationを使ったプロジェクトの設定については以下を参考にしました。
https://note.com/npaka/n/nc24ba42aa710

iOSビルド設定に関してはこちらにも書かれていますね。
https://qiita.com/ippo/items/d9c046bb365e2666ffdc#%E3%83%93%E3%83%AB%E3%83%89%E8%A8%AD%E5%AE%9A

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ClusterCreatorKitのトリガーギミックチョット分かる

この記事は
1月のお題企画「記事求ム!クリエイターキットライターズ」|バーチャルSNS cluster (クラスター)|note
のお題記事です。

ClusterCreatorKit

バーチャルSNS Clusterでワールドを作ったりゲームを作ったり、あるいはアイテムを作ったりできるSDKです。
詳しくは Cluster Creators Guide – バーチャルワールドをつくろう! 見てみてください

トリガーギミック(+オペレーション)

Clusterでは、通常のユーザスクリプト(.cs)は使うことが出来ません。
なので、何か「仕組み」的なものを作りたい場合、ClusterCreatorKitで用意されている

  • アイテム
  • トリガー
  • ギミック
  • オペレーション

の組み合わせで実現する必要があります。
いわゆるノンコーディングというやつですが、チョイチョイ罠があったりして、よくわからないという声をGAMEJAM中にも多く見かけたので、入門解説的に説明してみようと思います。

なるべく誰にでも分かるように書きますが、プログラマ寄りの目線なので、プログラムっぽくなるのはご容赦を。

なお、著者の理解の範囲で書いていますので、間違ってるよ!とかあればガンガン突っ込んでください:yum:

Item

基本中の基本。
Clusterで何かしらの仕組みを持つものは基本的に「Item」である必要があります。
とはいえ、TriggerとかGimmickを付ければ、勝手に付いてくるのであんまり意識する必要は無い……のですが、
Itemは親子関係を持てない 超重要。
Itemは親子関係を持てない 重要なことなので二回言った。
つまり、親子関係のあるItem間で何か作用させる、という仕組みは実現できないので、別の方法を探す必要があります。
なんか2つ以上連動させたいなー、ってときは親のGameObjectで包むか、Constraintとか使って兄弟的に扱うのが定番っぽい。
(どちらもUnityの標準の機能です)

image.png

Trigger/Gimmick

これは基本的にセットで考えましょう。
イベントを発火するのがTrigger、実際に動作するのがGimmickです。

image.png
TriggerとGimmickは同じアイテム内に同居することもあるので、紛らわしいのですが、
Triggerはテレビのリモコン、Gimmickはテレビと考えるとわかりやすいかと。
つまり、リモコンは信号を出すだけで、実際に仕事をするのは、テレビ本体ということです。
image.png

Key

多分、CCK入門者が最初に躓くところ。
Keyって何???Itemの名前???え、違うの????
プログラム寄りの素養を持っている方ほど混乱しやすい気がします。

CCKではすべてのやり取りがMessageで処理されており、その識別子がKey、つまりこんな感じ。
image.png

これでもまだ直感的ではないので、Keyは関数名という理解がわかりやすいのでは?と思います。
つまり、こう。
image.png
ギミックを付ける=機能を付ける=関数を生やす、というのはわりとしっくり来るのではないかな?と。

例えば、的あてゲームの作り方 – Cluster Creators Guideで説明されている仕組みはこんな感じです。
(単純化のために一部削ってあります)
image.png
単純に考えると、弾を前に進ませるのは銃では??みたいになるのですが、
現実の中でも薬莢を叩くまでが銃で、弾は勝手に爆発して飛んでいきます。
あるいは、魔法的に考えても、前に進むのは魔法の機能でしょう。

Target

CCK入門者が最初に躓くところ第二弾。
TargetってItemじゃないの??よく分からん。Globalにしちゃえ。Keyが混線してうわわあぁぁぁぁぁ!!!

Targetはトリガーとギミックのスコープ(範囲)です。
image.png

バランス感覚のいい鳩…ではなく、ロボットに持たれている「Zone」に着目します。
何故ならば、Triggerをコントロールできるのは、Itemだけだからです。

Zone自身はThisです
Zoneを持っているロボットはLocalPlayer(Owner)です。
他のプレイヤーはPlayerです。
どのプレイヤー?というのはCollidedItemOrPlayerなどで指定(検出)する必要があります。
自分自身以外のアイテムはItem(SpecifiedItem)です。
どのアイテム?という部分はCollidedItemOrPlayerまたはPrefab化の時点で指定する必要があります。
上記すべてを包含する、ワールド全体がGlobalです。

スコープ、と表現したのは、TriggerとGimmickでスコープを一致させる必要があるためです。
Thisで発火されたトリガーはThisで拾われる必要があり、
Itemで受けるトリガーはSpecifiedItemで発火する必要があります。
逆に言えば、スコープが一致していなければ拾われないので、This.hitOwner.hitは混線しません。
ややこしくなるから使わないけどね!!!!

Parameter Type

最初はSignalで困らないけど、ちょっと複雑にしようとすると詰まるところ。
関数の引数型です。
image.png
当然、トリガーとギミックで型が一致していないと、死にます。
コンパイルエラーも動作エラーも出してくれないので、混乱して苦しむことが多いですが、そのへんは動的メッセージングでやりとりしているCCKの仕様上、仕方ないですね。。。

各ギミックの詳細ページに、使える引数と動作が書いてあるので、それを見て決めましょう。
ギミック - Cluster Creator Kit ドキュメント

基本的かつ、最初のうち忘れがちなのは、Parameter Typeの指定はギミック側にある、ということです。
Target(スコープ)もParameter Type(引数型)も、呼び出し先のギミック(関数)である程度決まっているので、
逆算的にトリガーの設定を作ることになります。
まあつまりはギミック=インタフェースオーバーライド関数なわけですな。

Gimmickの対象

Clusterで何かしらの仕組みを持つものは基本的に「Item」である必要がありますと最初の方で書きましたが、これ実は嘘です。
代表的なところではSet Game Object Active Gimmick(オブジェクトをアクティブ化する)はGameObject(Unityの基本型)であれば何でも付けられます。
まあ、実際にはActive化じゃなくてCreateしたいケースの方が圧倒的に多く、CreateするにはItemじゃないと…となって発狂するのですが。:scream:

オペレーション(ロジック)

ハロクラなどで長らくロジックと呼ばれていたため、未だになかなか本名で呼んでもらえない子。
トリガーを発火するギミック、あるいはギミックの付いたトリガー。
タイマー(一定時間待ってから発火)とかLottely(くじ引き発火)とかですね。
なお、(本来の)Logicさんはラスボスすぎるので余白が足りない。。。(理解も足りない)
image.png

まとめ

なるべくイメージとして覚えやすいように図解しつつ、自分の中でも整理してみました!
書いてて思ったのですが、トリガー/ギミックではなくギミック(本体)/トリガー(発動ポイント)と表現する方がいいのでは?と思いました。ギミックの無いトリガーはただの信号発生器なので。
ギミックはイメージ付いてるので後で~、などとやっていると、GAMEJAM終盤で死ぬことになりますし。:expressionless:

というわけで、トリガーギミックチョット分かる、でした。
モット分かる、色々工夫する記事は、つよつよクリエイターさんに期待します。
よいクラスターライフを!











おまけ:Collider

ゲーム的目線でCCKで何かしら作ろうと思った場合、意外とハマりがちなCollider

  • 理由1:CCKのバグで、Unity上ではColliderが発火しないことが多い
  • 理由2:Colliderがシビアすぎると持ったりぶつけたりといった操作がスカって辛い

どちらも、物理コライダーと衝突判定コライダーを別々に作っておくとよいです。
衝突判定の方はisTrigger=Trueにしておくのが超重要
image.png

おまけ2:Animatorを活用しよう

Animatorというと「アニメーションを操作するもの」と思いがちですが、Unityにおけるアニメーターは、エディタ上で操作できるものであれば大抵のものが操作できます
例えば、重力の強さを変えるとか、一部のアイテムの表示切り替えなどなど。
そして、Set Animator Value Gimmickを組み合わせれば、条件分岐などにも対応可能です。
中途入室では同期がズレるなどの問題もありますが、短時間のアクションや効果であれば、かなり選択肢が広がります。
もちろん、Animatorの知識は必要になりますが、UnityのAnimator解説記事は世の中に大量にありますので、ちょっと頑張ってみるのは、ありなのではないでしょうか?

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unityで簡単なTPS操作をサクッと実装【キーボードWASD】

はじめに

初心者にとってUnityでキャラクターを操作する方法は結構複雑だなぁと感じたので、今回は簡単な例をスクリプトとしてまとめました。InputSystemの登場によって入力とアクションを疎結合にしやすくなるなど、便利な機能が増える一方、その場限りでちょっと試したい程度のときの実装コストは高くなりがちな印象です。

完成イメージ

WASD-移動
wasd-image
IJKL-視点 Space-ジャンプ
wasd-image wasd-image

結論

以下のファイル(C#)をつくって、動かしたいキャラクターに貼り付ければ完成
実際のコードは長くなるので、この記事の一番下の方に置いておきます。DefaultPlayer.cs

DefaultPlayer.cs
public class DefaultPlayer
{...}

準備と前提

キーボードからの入力を受け付ける手段として、新しい機能であるInputSystemを採用しています。
これはUnity2019.4以降を主な対象とした、従来のInputManager等(UnityEngine.Input)の代替手段です。
2020年1月現在ではデフォルトでは有効になっていないプロジェクトファイルが多いため、先にこれを有効にしておきます。

[1.Window - Package Manager] > [2.Unity Resistry] > [3."inputsystem"と検索] > [4.install]
inputsystem.png
インストールしたあと、誘導に従っていくことでUnityが再起動する場合があります。

(応用) キーによる操作を追加したい場合

移動速度やジャンプの高さを変更したり、任意のキーにオリジナルな操作を割り当てることができます。
先述のDefaultPlayer.csをプロジェクト内に置いたまま、CustomPlayer.csを作りましょう。
DefaultPlayerではなくCustomPlayerの方をキャラクターに貼り付けます。

  • onStart() -- 押下開始
  • onMiddle() -- 押下中
  • onEnd() -- 押下終了

メソッドの中でplayerインスタンスに対して操作ができます。
操作できること一覧は IPlayerインターフェースの定義元を参照してください。

CustomPlayerテンプレ.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using static UnityEngine.InputSystem.Keyboard;

// *****************************************************************************
// CustomPlayer.cs
// *****************************************************************************
// カスタム設定のプレイヤー 例
public class CustomPlayer : Player
{
    // 前に動くスピード
    override public float moveSpeed ()
    {
        return 2.0f;
    }
    // プレイヤーの高さ
    override public float height () {
        return 2.0f;
    }

    // デフォルト値で良いときは省略可
    // ジャンプの高さ
    // override public float jumpHeight ()
    // 重力の大きさ
    // virtual public float gravity ()
    // カメラ回転の速さ
    // virtual public float lookSpeed ()

    // 入力に使うキーを列挙
    override public Input[] keyInputs ()
    {
        return new Input[]
        {
            ...
            ここに使用したいカスタムキーを追加していく
            ()
            new T (), // T
            new E (), // E
            new S (), // S
            new Space () // スペース
            ...
        };
    }
}

// *****************************************************************************
// T.cs ファイルに分ても良い
// *****************************************************************************
// キーボードの「T」を押して入力テストをする例
public class T : Input
{
    public KeyControl key ()
    {
        return current.tKey; // 入力に使うキーを指定: キーボードの「T」は"tKey"
    }

    public void onStart (IPlayer player)
    {
        Debug.Log ("テスト 開始"); // キーを押し始めた時: ログ出力
    }

    public void onMiddle (IPlayer player, float deltaTime)
    {
        Debug.Log ("テスト 実施中"); // キーを押している間: ログ出力
    }

    public void onEnd (IPlayer player)
    {
        Debug.Log ("テスト 終了"); // キーを押し終わった時: ログ出力
    }
}

...
(省略) E, S, スペースキーのクラス〜
...

実装の説明

さて、ここで少しだけ実装に触れておきます。キーワードを箇条書き。

  • CharacterController
    • Unityでキャラクターを移動操作する方法は大きく分けて3つ
      • 1. transformを変更
      • 2. RigidBodyで物理演算
      • 3. CharacterControllerを使う
    • 3を採用、物理演算を無視することで処理自体を軽くし、シンプルでかつ軽快なキャラクター操作ができる反面、氷で滑って移動(摩擦)や壁に押し出される(外部からの応力)ことは自動ではできなくなる。重力もコードによって再現。
  • ポーリング
    • 一般的な入力処理の分類
      • 1. イベント通知型
      • 2. ポーリング型
    • 例えばボタンが押された時に、ボタンさんが「押されたよ」とわざわざ教えに来てくれるのが1、ボタンが押されていないかどうかをこちらから定期的に確認しにいくのが2。
    • イベントの方が無駄な処理は減らしやすいが、今回はキャラクターを毎フレーム移動させるなどの処理をどのみち行うため、フレームごとにキー押下を確認する 2 で実装。特に重い処理はないので誤差の範囲内。
  • アニメーション
    • キャラクターの体が固まったまま動いてしまうのをなんとかする
    • 今後コードを追記予定
    • 別の記事にするかも
  • マウス入力
    • 複雑化するので今回は見送り
    • 外付けコントローラーも同様

DefaultPlayer.cs

最後にコピペ用のスクリプトを貼っておきます。
なお筆者は、趣味でC#コードを書くときは一般的な命名ルールやC#特有機能をガン無視すると誓っておりますので、気になる方は大文字小文字や動詞名詞、コメント記法を調整してください。

DefaultPlayer.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.InputSystem;
using UnityEngine.InputSystem.Controls;
using static UnityEngine.InputSystem.Keyboard;

//***************************************************************
// DefaultPlayer.cs
//***************************************************************
// デフォルト設定のプレイヤー
public class DefaultPlayer : Player
{ }

//***************************************************************
// IPlayer.cs
//***************************************************************
// プレイヤーに対する操作一覧
public interface IPlayer
{
    // 足が地面についているかどうかを取得
    public bool isGrounded ();
    // どちらを見ているかを取得
    public To direction ();
    // to方向 に deltaTime秒間 移動する
    public void move (To to, float deltaTime);
    // ジャンプ
    public void jump ();
    // to方向 に 体を向ける
    public void turn (To to);
    // 上方を見る deltaTime秒間
    public void lookU (float deltaTime);
    // 左方を見る deltaTime秒間
    public void lookL (float deltaTime);
    // 下方を見る deltaTime秒間
    public void lookD (float deltaTime);
    // 右方を見る deltaTime秒間
    public void lookR (float deltaTime);
}

public enum To
{
    // Forward
    F,
    // Left
    L,
    // Right
    R,
    // Back
    B
}

//***************************************************************
// Input.cs
//***************************************************************
public interface Input
{
    // key for input
    public KeyControl key ();
    // start of pushing
    public void onStart (IPlayer player);
    // pushing
    public void onMiddle (IPlayer player, float deltaTime);
    // end of pushing
    public void onEnd (IPlayer player);
}

//***************************************************************
// Player.cs
//***************************************************************
// abstract Player
public abstract partial class Player : MonoBehaviour
{
    // CACHE
    CharacterController controller;
    Input[] inputs;
    float ySpeed = 0;
    Camera mainCamera;
    Transform fAnchorTransform;
    Animator animator; // ToDo: - enable to access animator

    // SETTING
    virtual public float moveSpeed ()
    {
        return 5.0f;
    }
    virtual public float jumpHeight ()
    {
        return 1.0f;
    }
    virtual public float gravity ()
    {
        return 9.8f;
    }
    virtual public float lookSpeed ()
    {
        return 50.0f;
    }
    virtual public float height ()
    {
        return 1.0f;
    }

    // keys to input
    virtual public Input[] keyInputs ()
    {
        return new Input[]
        {
            new DefaultW (),
                new DefaultA (),
                new DefaultS (),
                new DefaultD (),
                new DefaultSpace (),
                new DefaultI (),
                new DefaultL (),
                new DefaultJ (),
                new DefaultK ()
        };
    }
    // anchor object for definition the forward derection
    virtual public GameObject forwardAnchor ()
    {
        return Camera.main.gameObject;
    }

    /// MonoBehaviour Start()
    void Start ()
    {
        this.controller = this.gameObject.AddComponent<CharacterController> ();
        this.controller.center = new Vector3 (0, this.height () / 2, 0);
        this.controller.height = this.height ();
        this.inputs = this.keyInputs ();
        this.fAnchorTransform = this.forwardAnchor ().transform;
        this.mainCamera = Camera.main;
        this.animator = this.GetComponent<Animator> ();
    }

    /// MonoBehaviour Update()
    void Update ()
    {

        // refresh ySpeed
        if (this.isGrounded () && this.ySpeed < 0)
        {
            this.ySpeed = 0;
        }
        else
        {
            this.ySpeed -= this.gravity () * Time.deltaTime;
        }

        // use gravity
        var v3 = new Vector3 (0, this.ySpeed, 0);
        this.controller.Move (v3 * Time.deltaTime);

        // check input
        foreach (var input in this.inputs)
        {
            if (input.key ().wasPressedThisFrame)
            {
                input.onStart (this);
            }
            if (input.key ().isPressed)
            {
                input.onMiddle (this, Time.deltaTime);
            }
            if (input.key ().wasReleasedThisFrame)
            {
                input.onEnd (this);
            }
        }
    }
}

//***************************************************************
// Player+IPlayer.cs
//***************************************************************
public abstract partial class Player : IPlayer
{
    public bool isGrounded ()
    {
        return this.controller.isGrounded;
    }
    public To direction ()
    {
        var form = this.transform;
        var diffF = (-(this.fAnchorTransform.forward) - form.forward).sqrMagnitude;
        var diffL = (this.fAnchorTransform.right - form.forward).sqrMagnitude;
        var diffB = (this.fAnchorTransform.forward - form.forward).sqrMagnitude;
        var diffR = (-(this.fAnchorTransform.right) - form.forward).sqrMagnitude;
        var minDiff = diffF;
        var minTo = To.F;
        if (diffL < minDiff)
        {
            minDiff = diffL;
            minTo = To.L;
        }
        if (diffB < minDiff)
        {
            minDiff = diffB;
            minTo = To.B;
        }
        if (diffR < minDiff)
        {
            minDiff = diffR;
            minTo = To.R;
        }
        return minTo;
    }
    public void move (To to, float dTime)
    {
        var direction = Vector3.zero;
        switch (to)
        {
            case To.F:
                direction = this.fAnchorTransform.forward;
                break;
            case To.L:
                direction = -(this.fAnchorTransform.right);
                break;
            case To.B:
                direction = -(this.fAnchorTransform.forward);
                break;
            case To.R:
                direction = this.fAnchorTransform.right;
                break;
        }
        var v3 = direction * this.moveSpeed ();
        this.controller.Move (v3 * dTime);
    }
    public void jump ()
    {
        if (this.isGrounded ())
        {
            this.ySpeed += Mathf.Sqrt (this.jumpHeight () * 3.0f * this.gravity ());
        }
    }
    public void turn (To to)
    {
        var direction = Vector3.zero;
        switch (to)
        {
            case To.F:
                direction = this.fAnchorTransform.forward;
                break;
            case To.L:
                direction = -(this.fAnchorTransform.right);
                break;
            case To.B:
                direction = -(this.fAnchorTransform.forward);
                break;
            case To.R:
                direction = this.fAnchorTransform.right;
                break;
        }
        var v3 = new Vector3 (direction.x, 0, direction.z);

        this.mainCamera.transform.parent = null;
        this.transform.forward = -(v3);
        this.mainCamera.transform.parent = this.transform;
    }
    public void lookL (float deltaTime)
    {
        this.mainCamera.transform.RotateAround (this.transform.position, -(Vector3.up), this.lookSpeed () * deltaTime);
    }
    public void lookR (float deltaTime)
    {
        this.mainCamera.transform.RotateAround (this.transform.position, Vector3.up, this.lookSpeed () * deltaTime);
    }
    public void lookU (float deltaTime)
    {
        this.mainCamera.transform.RotateAround (this.transform.position, -(this.fAnchorTransform.right), this.lookSpeed () * deltaTime);
    }
    public void lookD (float deltaTime)
    {
        this.mainCamera.transform.RotateAround (this.transform.position, this.fAnchorTransform.right, this.lookSpeed () * deltaTime);
    }
}

//***************************************************************
// DefaultW.cs
//***************************************************************
// W (default)
public class DefaultW : Input
{
    public KeyControl key ()
    {
        return current.wKey;
    }
    public void onStart (IPlayer player)
    {
        player.turn (To.F);
    }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.move (To.F, deltaTime);
    }
    public void onEnd (IPlayer player)
    {

    }
}

//***************************************************************
// DefaultA.cs
//***************************************************************
// A (default)
public class DefaultA : Input
{
    public KeyControl key ()
    {
        return current.aKey;
    }
    public void onStart (IPlayer player)
    {
        player.turn (To.L);
    }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.move (To.L, deltaTime);
    }
    public void onEnd (IPlayer player)
    {

    }
}

//***************************************************************
// DefaultS.cs
//***************************************************************
// S (default)
public class DefaultS : Input
{
    public KeyControl key ()
    {
        return current.sKey;
    }
    public void onStart (IPlayer player)
    {
        player.turn (To.B);
    }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.move (To.B, deltaTime);
    }
    public void onEnd (IPlayer player)
    { }
}

//***************************************************************
// DefaultD.cs
//***************************************************************
// D (default)
public class DefaultD : Input
{
    public KeyControl key ()
    {
        return current.dKey;
    }
    public void onStart (IPlayer player)
    {
        player.turn (To.R);
    }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.move (To.R, deltaTime);
    }
    public void onEnd (IPlayer player)
    { }
}

//***************************************************************
// DefaultSpace.cs
//***************************************************************
// Space (default)
public class DefaultSpace : Input
{
    public KeyControl key ()
    {
        return current.spaceKey;
    }
    public void onStart (IPlayer player)
    {
        player.jump ();
    }
    public void onMiddle (IPlayer player, float deltaTime)
    { }
    public void onEnd (IPlayer player)
    { }
}

//***************************************************************
// DefaultI.cs
//***************************************************************
// I (default)
public class DefaultI : Input
{
    public KeyControl key ()
    {
        return current.iKey;
    }
    public void onStart (IPlayer player)
    { }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.lookU (deltaTime);
    }
    public void onEnd (IPlayer player)
    { }
}

//***************************************************************
// DefaultK.cs
//***************************************************************
// K (default)
public class DefaultK : Input
{
    public KeyControl key ()
    {
        return current.kKey;
    }
    public void onStart (IPlayer player)
    { }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.lookD (deltaTime);
    }
    public void onEnd (IPlayer player)
    { }
}

//***************************************************************
// DefaultL.cs
//***************************************************************
// L (default)
public class DefaultL : Input
{
    public KeyControl key ()
    {
        return current.lKey;
    }
    public void onStart (IPlayer player)
    { }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.lookR (deltaTime);
    }
    public void onEnd (IPlayer player)
    { }
}

//***************************************************************
// DefaultJ.cs
//***************************************************************
// J (default)
public class DefaultJ : Input
{
    public KeyControl key ()
    {
        return current.jKey;
    }
    public void onStart (IPlayer player)
    { }
    public void onMiddle (IPlayer player, float deltaTime)
    {
        player.lookL (deltaTime);
    }
    public void onEnd (IPlayer player)
    { }
}

以上です。
最後まで読んでくれてありがとう!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Unity2020 WebGL 9割まで読み込めるがアプリが起動しない不具合の解決方法

目次

  1. 不具合の内容
  2. 解決方法
  3. 参考文献
  4. その他

不具合の内容

Unity2020のWebGLビルドで作成したアプリを起動しようとしたものの、9割までデータを読み込めるがアプリが起動しない不具合の解決方法です。
Unity -> File -> Build Settings -> Build and Run でアプリを起動できるのですが、GitHub Pagesなどにアップロードした場合に起動しなくなります。

この画面のままで固まってしまう現象です。
スクリーンショット 2021-01-10 11.30.25.png

Chromeの右上の「︙」ボタン -> その他のツール -> ディベロッパーツールの中にあるConsoleのウィンドウに以下のエラーが表示されていました。

スクリーンショット 2021-01-10 11.26.24.png

:8887/Build/アプリ名.framework.js.gz:1Uncaught SyntaxError: Invalid or unexpected token

アプリ名.loader.js:1 Uncaught ReferenceError: unityFramework is not defined at HTMLScriptElement.r.onload (アプリ名.loader.js:1)

解決方法

Unity -> File -> Build Settings -> Player Settings -> Publish Settings -> Decompression Fallbackにチェックを付けましょう。
image.png

参考文献

その他

この不具合に対する回答に、「Player Settings -> Publish Settings -> Compression FormatをGzipやBrotliからDisableに変更する。」といったものがありました。この方法だとアプリのサイズが例えば37MBから96MBに膨れ上がってしまうため、あまり推奨はしないです…。もし本記事の方法でも解決しなかった場合に試していただくと良いかもしれません。
Unityのバージョン:2020.2.1
パソコン: Mac OS Catalina 10.15.7
何か質問などありましたらコメントしていただけると助かります。m(_ _)m

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

自分で撮影した360度動画をOculus Questで描出する方法のまとめ

Oculusの設定

1) OSを最新版アップデートする
アップデートがうまく行かない場合は、Oculusの初期化を行うと、OSがアップデートされる
2) Oculusで開発者モードを設定する
開発環境を設定するを参考に進める。OculusIDとfacebookIDを統合していると進めやすい。

Unity上に360度画像を表示

とりあえず、Unity上360度画像を出すまでは、Unityで360度動画を見る方法(Unity2018.1.0)の記事を参考に設定する。
ただし、oculus Goでの設定のため、Oculusの設定部分は参考にしない。

Oculus統合パッケージ(Oculus Integration)のインストール

Asset StoreからOculus統合パッケージ(Oculus Integration)をインポートする。

Unityの設定

Unity設定を構成するを見ながら設定を行っていく。

build settings

スクリーンショット 2021-01-10 7.55.42.png

Runデバイスの部分に自身のOculusが認識されない場合は、上述のOculsの開発者モード(開発環境を設定するを参考)を設定した上で、開発とテスト用にデバイスを有効にするを参考に設定する。その後refreshすると自身のデバイスが表示されるはず。

* すでに記載済みですが、Oculus Quest2の販売に伴い、OculusIDとfacebookIDの統合が進んでいるため、バラバラな場合は統合しておくと話が進みやすいです。

project settings

一回ビルドセッティングスを閉じて、上記を参考にして、プロジェクトセッティングを進める

カメラの設定

スクリーンショット 2021-01-10 9.07.20.png
Oculus上で360度で画面を表示するために、main cameraを削除し、Assets→Oculus→VR→PrefabsないのOVRCameraRigをHierarchyにドラッグ。
OVRCameraRig→TrackingSpaceのCenterEyeAnchorにSkyboxをadd componentし、表示したい画像のマテリアルを設定する。

Built and Run!!

上記設定がうまくいけば、自分で撮影した360度画像がOculus questで表示されます!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】依存性注入とは?と、なっているのでZenject(Extenject)を入門してみた

最初に

どうも、ろっさむです。

今回は「依存性注入」とは何なのかを理解するために、Zenjectを入門してみる手順をまとめたものとなります。

開発環境

  • Unity:2020.2
  • Extenject:9.2.0

依存性注入とは何か

英語だと「Dependency Injection」、通称「DI」と呼ばれているものです。依存性注入と言われていますが、「Dependency」を「必要なもの」とか「ないと困るもの」とかのイメージで考えると良さそうです。

参考:"Dependency"を使って、パンチの効いた制約関係の表現をする

DI自体は、「IT用語辞典バイナリ」にて以下のような説明がされています。

DIとは、プログラミングにおけるデザインパターン(設計思想)の一種で、オブジェクトを成立させるために必要となるコードを実行時に注入(Inject)してゆくという概念のことである。

参考:IT用語辞典バイナリ > DI

つまりクラスA内で必要となるクラスBの機能を、必要となったタイミングで引っ張ってくる感じです。そうすることで、クラス間での密な結合を防ぐことができます。よくわからんなーって人は後ほど記載する、使用例とサンプルコードを確認してみてください。

この引っ張ってくる方法を提供してくれるのが、今回紹介する「Extenject」となります。

Extenjectとは

DIを実現するためのフレームワークです。

こちらはUnityのアセットストアからインポートを行うことができます。

Extenject Dependency Injection IOC
image.png

こちらは元々「Zenject」というフレームワークをメインで管理していた方が、会社の退社後に個人でメンテを始めたフレームワークのようです。現在は「Extenject」の方が最新のUnityへの対応などが行われているため、これから使用する場合は「Extenject」をインポートすることをお勧めします。

参考:Zenjectをメンテナンスするために生まれたExtenjectというリポジトリ

import 方法

先ほどのアセットストアのリンクからマイアセットに追加する > Unityで開く > Package ManagerにてExtenject Dependecy Injection IOC を検索するか、窓を開く > DL > Importまでを行ってください。

そうすると Assets > Plugins > Zenjectというフォルダが作成されます。早速試していきましょう。

使用例とテストコード

例えば、MyCharacterクラスがあるとします。

今回は適当にHierarcy > 3D Object > Sphereを作成して、MyCharacterという名前をつけておきます。

image.png
image.png

今回はこのキャラクターの移動制御を行うための処理をExtenjectを使用して構築していきます。

ここでなぜExtenjectを使用するのかというと、input処理をUnityEngine.Input以外からも受け付けられるようにするためです。
例えば、現在主流なのはUnityEngine.Inputを用いた移動処理の実装だと思いますが、この他にもUnity2020から使用可能となったInput SystemRewiredという入力管理系アセットに差し替えたくなる時が来るかもしれません。その場合、Input処理を変更するのに、UnityEngine.Inputだけに依存した処理にしていると、多くのリファクタが発生してしまいます。
今回のMyCharacterクラスで考えてみましょう。

移動処理用のコンポーネントとしてInputForMoveを用意しておきます。
image.png

このUpdate()内部では、UnityEngine.Inputを直接参照しています。この状態から、例えば先ほどあげたInput SystemでのInput処理を使いたい場合は、このUpdate()内部を書き換える必要が出てきます。
image.png

interfaceを使用しても良いのですが、結局どこかでオブジェクトをnew()する必要があり、プロジェクトが大規模になるほど、その場所はコードの奥深くに位置する可能性が高くなります。

ここで、new()を一つの場所に集約しつつも、疎結合となるような作り方を行うためにExtenjectを使用します。

まずはinterfaceと各実装を用意

Inputクラスが共通で持つ処理をInterface側に記述をし、各Inputクラスで実装を行うようにしてみましょう。

Interface作成

雑にIInputtableという名前にしておきます。

using UnityEngine;

public interface IInputtable
{
    Vector3 InputForMove();
}

このVector3値は移動するために使用する、Input情報を用いて作成したものとなります。

Inputクラス作成

まずはKeyboard入力用を作成してみます。

using UnityEngine;

public class InputFromKeyboard : IInputtable
{
    public Vector3 InputForMove()
    {
        return new Vector3(Input.GetAxis("Horizontal"), 
            0, Input.GetAxis("Vertical"));
    }
}

オブジェクトにアタッチ用のスクリプト作成

実際に動かすオブジェクトに対してアタッチするスクリプトを作成し、内部でInput入力を受け取って位置を更新する処理を記述します。

using UnityEngine;
using Zenject;

public class InputForMove : MonoBehaviour
{
    [Inject]
    private IInputtable _inputObject;

    void Update()
    {
        if(_inputObject != null)
        {
            Move(_inputObject.InputForMove());
        }
    }

    void Move(Vector3 vec)
    {
        var position = transform.localPosition;
        transform.localPosition = position + vec;
    }
}

ここでの_inputObject[Inject]を付けることによって、中身が「注入」されます。

さて、問題はこの_inputObjectの中身に、どうやってInputFromKeyboardを入れるかですが、ここを解決するにはInstaller用のクラスを作成する必要があります。

Installerの作成

Installerクラスは、Zenjectの注入を行うにあたって、必要となる依存関係の定義を行うクラスになります。

ProjectタブなどからZenject > Mono Installerを選択し、作成してみましょう。
image.png

今回はIInputtableのインターフェース型の変数に注入する場合、InputFromKeyboardクラスを使用したいので、以下のように記述してみます。

using Zenject;

public class SampleInstaller : MonoInstaller
{
    public override void InstallBindings()
    {
        Container
            .Bind<IInputtable>()
            .To<InputFromKeyboard>()
            .AsSingle();
    }
}

一つずつ要素を分解して確認してみましょう。

Container

注入するに当たって、

  • 要求される可能性のあるinterface
  • そのinterfaceに対応したクラス(このクラスのインスタンスを注入)
  • インスタンスの生成方法や、扱い方

を設定します。その設定先がこのContainerとなります。

Bind

「要求される可能性のあるinterface」をここで記述しています。
今回の場合だと、IInputtable型を必要とする変数、プロパティなどを記述しているクラス全てが対象となることを明示しています。ただし、このInstaller自体の影響範囲内に限ります(後述)。

To

「interfaceに対応したクラス(このクラスのインスタンスを注入)」をここで記述しています。
IInputtable型を必要とするクラスの変数などに、Toで示したInputFromKeyboard型のインスタンスを注入します。

AsSingle

「インスタンスの生成方法や、扱い方」をここで記述しています。
AsSingleだけではなく、他のパターンもあるので見ていきましょう。

項目 概要
AsSingle このContainer全体で、Toの際に定めたクラスのインスタンスを1つだけ持ち、そのインスタンスを再利用し続ける。シングルトン状態。基本的にこれがよく使われる。が、動的Bindには向いていない。
AsCached 既にインスタンス登録が行われている場合は、そのインスタンスを使用する。UnBindするとインスタンスの参照は破棄されるため、動的にBindを行いたい場合はこちらを使用する。
AsTransient インスタンスを再利用せず、Toの際に定めたクラス型のインスタンスが要求される度に、新しいインスタンスを使用する。

なので、今回は「IInputtable型のインスタンスが必要になった際には、InputFromKeyboard型のインスタンスを一つだけ作成し、Containerに登録を行って、一つのインスタンスを使い回す」といった状態になります。

次に、このInstallerで記述した内容が、どこまで影響範囲を持つのかを設定する必要があります。

Contextを作成する

Zenject > Scene Contextから作成が可能です。
image.png

今回のContextではScene Contextを指定しているため、このContextが配置されているシーンが影響範囲として設定されます。

Contextの種類に関して更に詳しく知りたい方は以下の記事がおすすめです。

参考:【Unity】【Zenject】DIの影響範囲を指定するContextの使い方まとめ

作成されたScene Contextに対して、先ほど作成したSample Installerをアタッチしておき、それをScene ContextMono Installersに追加します。

image.png

これでSample Installerが走るようになりました。

実際に動作テストを行っても正常に動くかと思われます。

もしも開発途中で別のInputクラスが使用したくなっても、Installerクラス内部でのInputFromKeyboardを置き換えれば良いだけとなります。非常に便利ですね。

Extenjectにはまだまだ様々な機能があるようなので、今後もじっくり学習していきます。

また、以下の記事も今後学習を進めていくのに、有用かと思われますのでリストとして載せておきます。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む