20191115のUnityに関する記事は5件です。

unity初心者です。 unityのインストールの方法を教えてください。

unity初心者です。2019年度版のMacbook proを使っています。 ひよこのUnity2019入門のp17のインストールでつまずいています。
unity hubをapplicationsにドラッグ&ドロップでファイルを開こうとすると、 “Unity Hub”が悪質なソフトウェアかどうかをAppleでは確認できないため、このソフトウェアは開けません。 このソフトウェアはアップデートが必要です。詳しくは開発元にお問い合わせください。 と出てきて、止まっています。 具体的な解決策を教えてください。 よろしくお願います。

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

【C#,Unity】Dictionary<GameObject,float>をfloat基準で昇順にSortしようとしたら謎処理を挟んでいた話

追記【2019/11/15】

@albireo 様にアドバイスしていただいたSortedListの方が簡易的かつ無駄なく処理できたので
記事の末尾に追加しておきました。

この記事の狙い

自分の経験を書き記すとともに、有識者の方々からアドバイスや説明をもらえたらいいなぁ。
という期待。

あらすじ

Unityで画面内に映っているTargetを取得&自キャラとTargetの距離を取得できるようになったので、
これを「距離が近い順」で並べ替えたい!と思い、色々調べながら試してみることに。

本題

まず「画面内に映っているTarget」と「Targetまでの距離」、この二つの情報を紐付けるには
どうすればいいのかを調べるところから始めた。

これは調べると直ぐに出てきた、最初はList<>か配列[ ]でなんとかするしかないのかな?とか考えていたが、
調べてみるとDictionaryという超便利なクラスがあるではないか。
早速、意気揚々と使ってみる。

Dictionary<GameObject,float> ProvisionalValue = new Dictionary<GameObject,float>();
ProvisionalValue.Add(hit,target_distance); 

よし、これで情報の紐付けはできたな...!
後はこれを「距離が近い順」で並べ替えるだけだ...!

なんだ楽勝じゃん。勝ったな、風呂入ってくる。

ここからが本当の地獄だ...!

さて、ちゃちゃっとSortして終わらせるか~

ProvisionalValue.Sort();

...あれ、できない。なになに?DictionaryにはSortの定義が含まれていない?
あら、そうなのか...。じゃあどうやって並び替えするんだろう、調べてみるか...。

【C#入門】DictionaryのKey、Valueの使い方(要素の追加、取得も解説)

ははーん、Linqを使えば出来るのね。試したことが無いけど、やってみるか...。

ここで訳の分からないことをし始める(多分色々調べているうちにいろんな記事の情報が入り混じった)

えーっと、まずはソートする基準がいるから(?)floatだけをList<>で取得してからこれをOrderByで
並べ替えて...、あれ?Valueが定義が含まれていない...?(Listだから)

これは何かがおかしい...!という感じで混迷を極めた結果、こうなった。

    private static Dictionary<GameObject,float> hitsOB = new Dictionary<GameObject,float>();
    private static Dictionary<GameObject,float> ProvisionalValue = new Dictionary<GameObject,float>();

    {
        ProvisionalValue.Add(hit,target_distance);
        Sort(ProvisionalValue);
    }

    public static void Sort(Dictionary<GameObject,float> itemTable) //取得したオブジェクトと距離のデータを距離が近い順でソートするメソッド
    {
        IOrderedEnumerable<KeyValuePair<GameObject,float>> _table_1 =
            itemTable.OrderBy(selector => selector.Value);
            foreach(KeyValuePair<GameObject,float> pair in _table_1)
            {
                hitsOB.Add(pair.Key,pair.Value);
            }
    }

改めて見ると何やってるんだか、と思いました。

ProvisionalValueに格納したデータをOrderByで並べ替えて、並べ替えたデータを_table_1に格納して、
foreachでpairに一つずつ格納してhitsOBに順に格納....

いや、これ普通にProvisionalValueをOrderByでソートすればいいじゃん。

はい、というわけでそうした結果がこちら。

ProvisionalValue.Add(hit,target_distance);
ProvisionalValue.OrderBy(x => x.Value);

二行で終了。(なぜ俺はあんなムダな時間を...)

ふりかえり

そもそも混乱した理由は「よく理解していなかったから」につきます。
Dictionaryの値の取得方法すら理解していなかったが為に、KeyとValueがなんなのか分からずに使っていた
結果、今回のような無駄な時間を浪費する羽目になりました...。

まさしく「知識は身を助ける」を身をもって実感しました。

追加記事【SortedList】

先に紹介したDictionaryクラスは値を格納した後にOrderByでソートしていましたが、
SortedListクラスを使用すればわざわざソートする必要がなくなります。

しかし、注意しなければいけない点があります。
それはSortedList <TKey,TValue>では、キー(TKey)の値を基準にソートされます。

今回の例で行くと、先の紹介したDictionaryはDictionary<GameObject,float>としていましたが、
これだとTKeyの値に代入しているGameObjectのソート基準はNameの頭文字をABCD...順で並び替える為、
ソートの基準は距離で並び替えたかったので、順番を入れ替える必要がありました。

ということを踏まえた上で変更したものが以下の流れです。(距離の所得等は省いています)

private SortedList<float,GameObject> ProvisionalValue;

void Start()
{
   ProvisionalValue = new SortedList<float,GameObject>();
}
public void Sort()
{
   ProvisionalValue.Add(target_distance,hit);
   foreach(var item in ProvisionalValue)
   {
       Debug.Log("Distance : " + item.Key + " / Object : " + item.Value);
   }
}

これでコンソールに距離順で距離とオブジェクト名が表示されます。

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

Unityで物理演算を使いたくなかった話 ~横スクロールアクションゲームの開発~

物理演算を使いたくない理由

Unityの物理演算(Rigidbody周り)は大変便利な機能ですが、時に制御しきれないときもあります。
意図せず、Collisionを突き抜けてしまったりしますよね。
なにより、Unityの便利機能に甘えっぱなしな感じがちょっと嫌なので、今回はRigidbody、Rigidbody2Dを使わず、横スクロールアクションゲームを開発した時のことを書こうと思います。(Collisionは使います。)
qiita1.PNG
不思議の国のアリスが、ハートの女王などが仕掛ける罠をよけるゲームです。

Raycastを使う

本来、Rigidbodyを設定しないと、Collision周りのメソッド(OnCollisionEnterメソッドとか)が反応しないため、「何かに当たったときに、ダメージを負う」的な処理ができません。
なので、結局Unityの機能に甘える形にはなりますが、Rayを使用します。Rayを投げ、当たったCollisionの座標との距離を測ったりして、衝突判定の処理を実装します。
qiita2.PNG
これは、Debug.DrawRay()メソッドでキャラクターやトラップからRayを飛ばす様子をSceneウィンドウから確認したものです。白い線が当たり判定によるアリスの動きの処理をするためのRayで、鎌から伸びている赤い線が、アリスに当たった瞬間アリスにダメージを与える処理をするためのRayです。

床判定、壁判定

今回作るゲームは、スマートフォン向けのずっと横に走り続けながら、敵をかわしてスコアを伸ばすゲームなので、床の判定と壁の判定が必要になりました。
なので、まずはキャラクターから横向きと、下向きにRayを飛ばします。(左右に動く場合は、左右どちらにもRayを飛ばす必要がありますが、今回はずっと右向きに走るゲームなので、右向き、Vector3(1,0,0)の方向と下向き、Vector3(0,-1,0)の方向のみにRayを飛ばします。)

Alice.cs
    private bool IsCollision(Vector2 direction,float distance,float adjast = 0)
    {
        var position = transform.position;
        // Rayを飛ばす初期位置を調整する
        position.y += adjast;

        var raycastHit2D = Physics2D.RaycastAll(position, direction, distance);
        Debug.DrawRay(position, direction);

        // 何も検知できなかった場合、処理を中断し、falseを返す
        if (raycastHit2D.Length == 0)
            return false;

        foreach (var raycastHit in raycastHit2D)
        {
            if (raycastHit.collider
                && !raycastHit.collider.gameObject.Equals(gameObject))
                return true;
        }
        return false;
    }

※Rayを飛ばすオブジェクト自体にCollisionを付けている場合、Rayを飛ばす位置は自身のオブジェクトも含んで当たったことを通知します。なので自身と子オブジェクトの当たり判定を外す必要があります。

移動の処理

Rigidbodyを使う場合は、velocityなどを使えば簡単にオブジェクトを動かせます。
しかし、Rigidbodyを設定していないため、Tween系のライブラリを使うか、transform.positionを愚直に操作するしかありません。

本来であればTweenなどを使うべきかもしれませんが、面倒くさかったため、transform.positionの値を0.001fずつ動かすというヤバみの深い実装になりました。
(UIとかはTween使うと綺麗に動くので、Tween好きです。)

結論

カジュアルゲーム作るぐらいなら、普通にRigidBody使ったほうが良いと思いました。
ちょっと調べると、Rayも普通に処理として重いそうです。(なので、あらかじめ飛ばす距離などはできるだけ短く指定してあげるとましになるとか、ならないとか・・・)

あと、見た目が2.5Dの場合は、2D用のRayではなく、3D用のRayを使用したほうが開発しやすいのでは・・・と思いました。

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

DownloadHandlerTextureで取得した画像が解放されない

TL;DR

Destroy(texture)またはResources.UnloadUnusedAssets()
またはシーン遷移

DownloadHandlerTexture

DownloadHandlerTextureは画像をAssetBundleにしなくても使うことができます。
つまり、適当なhttps://example.com/yukamaki.jpgみたいなのを拾ってきてTexture2Dにまでしてくれるクラスです。
マニュアルを読む限り、textureプロパティにアクセスすることで追加のアロケーションは発生しないと書いてありますが、デコードはメインスレッドなのか、Rawなデータは破棄されているのかあたりが気になるところです。// 今度調べる
RawデータはHandlerのDisposeで消えそうな気がしますが…

AssetBundleで管理している場合はAssetBundle.UnloadでOKです。

Runtime生成Asset

ランタイム生成したAsset(Object)は基本的にこの扱いになり、DownloadHandlerTexture.textureはRuntime生成となるようです。
他にも、お馴染みInstanciate(prefab)new GameObject()new RenderTexture()などがこれに当たります。

C#のオブジェクトがGCされたら一緒に解放

class ManagedTexture2D : Texture2D
{
    public ManagedTexture2D(int width, int height) : base(width, height){}

    // GC時に呼ばれる
    ~ManagedTexture2D() => Destroy(this);
}

DownloadHandlerTextureのように、自分でnewしていないときには成すすべ無いので微妙…

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

New Input System をふわっと理解する ~大八耐2019 in Tokyoを添えて~

まえがき

 大八耐2019 in Tokyoに参加したときに気になっていたNew Input Systemを触ってみたのでふわっとまとめたみました。

*Input System - 1.0.0 時点での記事なので以降のバージョンと差異がある可能性があります。

Input System のインストール

 Input SystemはPackage Managerを通じてインストールします。
 *2019年11月時点では、まだpreview packageなのでShow preview packagesにチェックを入れないとリストに現れません。
スクリーンショット 2019-11-12 16.31.09.png
 次に、Project Settings > PlayerからActive Input Handlingを設定します。設定を反映させるにはUnity Editor の再起動が必要なので注意しましょう。
スクリーンショット 2019-11-12 16.40.14.png

Input Settings を作る

 次は、Project Settings > Input System Package からCreate settings assetを選択し、InputSystem.inputsettingsを作成します。
スクリーンショット 2019-11-12 17.37.55.png

 作成したInputSystem.inputsettings からOpen Input Setting Windowを選択し、Supported Devicesに入力を取りたいデバイスを登録します。
スクリーンショット 2019-11-13 16.17.18.png

Input Actionを使ってみる

 Input Actionは、入力があった際のイベントをデリゲートに登録しておくことで動作します。今回は、キーボードのスペースキーとスクリーンのタップと、マウスとタップの座標を取得してみます。
スクリーンショット 2019-11-14 22.24.06.png

GettingFromDevice.cs
using UnityEngine;
using UnityEngine.InputSystem;

public class GettingFromDevice : MonoBehaviour
{
    [SerializeField] private InputAction _jumpInput = default;
    [SerializeField] private InputAction _moveInput = default;

    private void Awake()
    {
        // A事前にイベントを登録しておく
        _jumpInput.performed += callbackContext =>
        {
            // Buttonの入力はfloat
            var value = callbackContext.ReadValue<float>();
            if (value > 0)
            {
                Debug.Log("On Jump.");
            }
        };

        _moveInput.performed += callbackContext =>
        {
            var value = callbackContext.ReadValue<Vector2>();
            Debug.Log($"position {value.x},{value.y}");
        };
    }

    private void OnEnable()
    {
        // Enable()で有効化しないと動作しない
        _jumpInput.Enable();
        _moveInput.Enable();
    }

    private void OnDestroy()
    {
        _jumpInput.Dispose();
        _moveInput.Dispose();
    }
}

 InputActionフィールドを定義し、インスペクタで各InputActionにBindした入力受けたときのイベントをInputAction.performedに記載します。この時InputAction.Enable()で有効かしておかないと動作しないので注意が必要です。
 CallbackContext.ReadValueで受け取る値は、インスペクタ上の各InputActionの歯車ボタン内のAction TypeとControl Typeから設定しています。
今回の例では、Jump InputはAction TypeはButton、Move InputはAction TypeはValue、Control TypeをVector2としています。

スクリーンショット 2019-11-14 22.32.46.png
スクリーンショット 2019-11-14 22.34.17.png

 ActionをButtonにした場合は、Control Typeはfloatになっています。

 キーボードのWASDをなど複数のキーを入力として取りたい場合、Add BingindではなくAdd 2D Vector Compositeを使います。上下左右のにそれぞれのキーをBind
することでVector2の入力を作ることができます。ただしMouseのPositionとは異なり座標ではなく-1から1の正規化された入力なので併用する場合は工夫が必要です。

スクリーンショット 2019-11-14 22.39.40.png

ActionMapを生成してみる

 先ほどは、InputActionフィールドを用意してデリゲートにイベントを登録しました。今度はActionMapを生成し、自動生成されたコードを継承してコールバックを受け取る方法を試してみます。先ほどと同じくキーボードのスペースキーとスクリーンのタップと、マウスとタップの座標を取得してみます。
スクリーンショット 2019-11-14 22.56.02.png
 Asset > Create > Input ActionsからSampleControles.inputactionsを生成し、SampleMapsの中に先ほどと同じくMoveActionとJumpActionをを設定しました。次にGeberate C# Classにチェックを入れてApplyすると同じフォルダに同名のクラスが生成されます。今度はこれを動かすコードを書いていきます。

ActionMapSample.cs
using UnityEngine;
using UnityEngine.InputSystem;

// 生成されたクラスはI[ActionMap]インターフェスを持っているので、これを実装します。
public class ActionMapSample : MonoBehaviour, SampleControls.ISampleMapsActions
{
    private SampleControls.SampleMapsActions _sampleMapsActions = default;

    private void Awake()
    {
        // SampleControlsに登録したActionMapを生成ます。
        _sampleMapsActions = new SampleControls.SampleMapsActions(new SampleControls());
        // SampleControls.ISampleMapsActionsが実装されたクラスをSetCallbacksに指定します。
        _sampleMapsActions.SetCallbacks(this);
    }

    // SampleControls.ISampleMapsActionsによって定義されたMoveActionのコールバック
    public void OnMoveAction(InputAction.CallbackContext callbackContext)
    {
        var value = callbackContext.ReadValue<Vector2>();
        Debug.Log($"position {value.x},{value.y}");
    }

    // SampleControls.ISampleMapsActionsによって定義されたJumpActionのコールバック
    public void OnJumpAction(InputAction.CallbackContext callbackContext)
    {
        var value = callbackContext.ReadValue<float>();
        if (value > 0)
        {
            Debug.Log("On Jump.");
        }
    }

    private void OnEnable()
    {
        // 忘れずEnabl()
        _sampleMapsActions.Enable();
    }

    private void OnDestroy()
    {
        // こっちはDisposeではなくDisable()
        _sampleMapsActions.Disable();
    }
}

 自動生成されたクラスはファイル名.I[ActionMap名]のインターフェースを持っています。これを実装したクラスをSetCallbacksで指定することで入力を取得できるようになります。この時、ReadValueで取得する型とControl Typeが一致していないとエラーになってしまいます。

入力の検知タイミングを指定する

 これまでJumpActionとしてキーボードやタップを入力にしましたが、現在の状態では押した時、離した時の両方で入力を受け取ってしまいます。これを回避するためにInteractionsを設定する必要があります。
スクリーンショット 2019-11-14 23.39.28.png
 上記のようにJumpActionのInteractionsにPressを、そしてTrigger Behavior
nにPress Onlyを設定しました。これによってJumpActionはButtonのPressのみを検知するようになりました。今回はJumpActionに対して設定しましたが、SpaceキーやTouchそれぞれ個別に設定することも可能です。

 *ここまで実装した方は気づいたかもしれませんがNew Input Systemは1フレームに複数回の入力を受け取ることができます。フレームに依存しないので処理落ち処理落ちした場合でも入力を受け取ることができますが、同じ処理を複数回しないために工夫する必要があります。

大八耐2019 in Tokyo

 大八耐2019ではパズルのピースを使ったテトリスっぽいものを作ってみました(完成しませんでした)。ピースを回転させて横一列に並べるというミニゲームで、New Input Systemを使ってキーボード入力からピースを左右に回転させました。
スクリーンショット 2019-11-14 23.53.12.png

SpinInputView.cs
using System;
using papicra.Scripts.Presentation.Presenter;
using UniRx;
using UnityEngine;
using UnityEngine.InputSystem;

namespace papicra.Scripts.Presentation.View
{
    public class SpinInputView : MonoBehaviour, PieceRollControls.IPieceRollActionActions, ISpinInputPort
    {
        private PieceRollControls.PieceRollActionActions _input = default;

        private readonly Subject<Unit> _spinClockwise = new Subject<Unit>();
        private readonly Subject<Unit> _spinUnClockwise = new Subject<Unit>();

        public IObservable<Unit> OnSpinClockwise() =>
            _spinClockwise.Publish().RefCount().ThrottleFrame(1);

        public IObservable<Unit> OnUnSpinClockwise() =>
            _spinUnClockwise.Publish().RefCount().ThrottleFrame(1);

        private void Awake()
        {
            _input = new PieceRollControls.PieceRollActionActions(new PieceRollControls());
            _input.SetCallbacks(this);
        }

        public void OnClockwise(InputAction.CallbackContext context)
        {
            if (context.ReadValue<float>() > 0)
            {
                _spinClockwise.OnNext(Unit.Default);
            }
        }

        public void OnUnClockwise(InputAction.CallbackContext context)
        {
            if (context.ReadValue<float>() > 0)
            {
                _spinUnClockwise.OnNext(Unit.Default);
            }
        }

        private void OnEnable()
        {
            _input.Enable();
        }

        private void OnDestroy()
        {
            _spinClockwise?.OnCompleted();
            _spinClockwise?.Dispose();
            _spinUnClockwise?.OnCompleted();
            _spinUnClockwise?.Dispose();
            _input.Disable();
        }
    }
}

 その時に使用したコードがこちらです。テラシュールブログ様 *2 を参考に結構さっくりと実装ができました(inputsettingsの項目に気がつかなくて入力が取れず沼ったのは秘密)。今回紹介したInputActionフィールドを定義してデリゲートにイベントを登録する方法、ActionMapをつかったコールバックを取る方法、ここでは紹介していないPlayerInputを使う方法など色々ありますが、個人的にはコールバックを取る方法が一番やりやすいなぁと感じました。インターフェースあると実装漏れないし楽でした。
 大八耐の時間内でゲームを完成させることはできませんでしたがNew Input Systemを触るいい機会になったので個人的には大満足です。ゲーム自体はちまちま作っていこうかな。

あとがき

 大八耐2019 in TokyoにてNew Input Systemを軽く触ってみたのでまとめてみました。BindingsやProcessorsなどなど調べ切れていないことが多々あるのでまたいずれ......。
 内容に誤りがありましたら、@sai_maple_にご連絡いただけると幸いです。

参考

*1 Input System documentation
*2 テラシュールブログ

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