20191002のUnityに関する記事は7件です。

uGUIよりも手前にメッシュのオブジェクトを描画したい

Canvasの設定を変更する

3種類のRender Modeを使い分ける

01.png

  • Screen Space - Overlay
  • Screen Space - Camera
  • World Space

① Screen Space - Overlay

常にUIが最前面で描画されます。

Canvas同士の描画順はSort Orderで制御することが可能です。
Sort Orderの値が大きいほど手前に描画されます

01.JPG

UIが最前面張り付きになるので、このモードの時にはMeshをuGUIよりも手前に描画することはできません。

MeshやParticleの描画順を前後させたい時にはScreen Space - Camera、もしくはWorld Spaceを使う必要があります。

Sort Orderが出てきた後で紛らわしいですが、Sprite RendererやParticle System、Mesh RendererにはSorting LayerOrder in Layerという2つのパラメーターが存在します。

これによってそれぞれの描画順を制御することができます。

Sorting Layer & Order in Layer

階層としてはSorting Layerの中にOrder in Layerがあります。

なのでSorting Layerで描画順を大別し、Order in LayerでLayer内の描画順を細かく制御します。

Sorting Layerは初期状態ではDefaultしかないので自分で追加してあげる必要があります。

02.JPG

BackとForwardを足しました。
上にあるほど奥に、下にあるほど手前に描画されます。

Sorting Layerが設定されていないと、基本的にはZ値を基に描画順は変わります。
カメラより遠いものを先(奥)に描画し、近いものは後(手前)に描画します。

03.JPG

真ん中の画像をDefaultをBackにし、他はDefaultのままにするとこのようになります。

04.JPG

描画順がZ値に関係なく、Sorting Layerの順で描画されるようになりました。

全てのSorting LayerをDefaultに戻し、一番奥の画像のOrder in Layerを1にしてみます。

05.JPG

Order in Layerが0のものより手前に描画されるようになりました。

② Screen Space - Camera

Order in Layerを使うためにモードをScreen Space - Cameraに変更します

ここで注意が必要なのがRender CameraがNoneのままだと、Screen Space - Overlayと描画結果が変わらないままになってしまいます。

06.JPG

Warningに書かれているようにScreen Space - Overlayと同じ挙動になります。

Cameraを設定した状態でPlane Distanceを使うことでカメラとUIの間にMeshを描画できるようになります。

Plane DistanceとはカメラとUIの距離を指しており、この値が例えば3だとカメラとUIの間に3m分のスペースがあるのでそこに配置されたオブジェクトはUIよりも手前に描画されます。

07.JPG

08.JPG

③ World Space

これは3次元空間にUIを配置するモードなので他のGameObjectと同じようにZ値を基に描画順が制御されます。

他のモードではUIが画面張り付きだったのに対し、回転した状態で配置することもできます。
普通のGameObjectのような感覚で扱うことが可能になります。

このモードでもSorting LayerやOrder in Layerを使って描画順を制御することが可能です。

uGUIよりも手前にメッシュのオブジェクトを描画するには

Screen Space - Cameraか、WorldSpaceを使うことで実現できます。

Mesh Rendererを使ったオブジェクトはSorting Layer、Order in Layerのパラメーターがインスペクター上にありませんが、スクリプトから変更可能です。

テラシュールブログさんの記事が参考になります。
UnityのSpriteとパーティクルとかモデルの描画順番について

Sorting LayerやOrder in Layerを使わなくても、基本的には手前に描画されてほしいものをカメラの近くに配置すれば問題ありません。

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

[Unity]NavMeshを改めておさらい。

UnityのNavMeshを改めておさらいするということで簡単なEnemyもどきが目的地へ行くものを実装していきます。

1,移動できる範囲を設定しよう

地面となる床を配置していきましょう。HierarchyのCreateから3DObject→Planeです。
1.PNG

とりあえず床が実装できましたね。
2.PNG

では早速移動できる範囲、NavMeshを実装していきましょう。
まず、Planeを選択してInspector(詳細)を表示します。
Inspectorにオブジェクトの名前が書かれている部分があります。その右にstaticと書かれている部分があり、staticの近くにある逆三角形をクリックしNavigation Staticと書かれている部分を選択します。
3.PNG

次に、上部にあるWindowタブからWindow→AI→NavigaitonでNavigationタブを出します。
4.PNG
すると、新しくNavigationタブが出てきます。
あとは簡単です。Navigationタブの4つある項目のうちBakeというものを選択します。
そして、下部分にあるBakeというボタンを押すだけです。
すると、Planeに薄い青の四角形が出てきます。
Bake.png

この青い部分が移動できる範囲となります。
これでNavMeshの設定が終了しました。
では次に、実際に動かしてみる、前に・・・。
カメラを少しいじります。
わかりやすいように上から観察するようにしましょう。
HierarchyのMainCameraを選択して以下のTransform情報を記入してください。
12.PNG
こうすると上から観察しているような視点になります。

2,Enemyを目的地まで移動させてみる

まず、動いてくれるEnemyを作ります。
先ほどPlaneを生成したときと同じ要領でCapsuleを生成します。
Hierarchy→3DObject→Capsuleで生成します。
わかりやすくするためにCapsuleの名前をEnemyに変えておきましょう。
Capsuleを選択してInspectorを表示します。Inspectorの上部にCapsuleと書いてある部分があるのでそこをEnemyに書き換えます。
6.PNG

・・・まだ少しわかりずらいですね。色も変えてあげましょう。
Projectというタブを選択してファイルのある部分で右クリックをし、Create→Materialと選択します。
7.PNG

作ったマテリアル選択してInspectorからAlbedoという項目を探します。
その右側にある白い四角形をクリックして好きな色を選択してあげます。僕は赤を設定します。

色を変えることができたら作ったマテリアルを押しながらHierarchyにいるEnemyまでもっていきます。Enemyの部分に青い枠が出てきたら離してあげます。すると、Enemyの色が変化しているはずです。
drag.png
こんな感じに。
9.PNG

次に、Enemyを選択してInspectorを開き、一番下にあるAdd Component(要素を追加する)を押します。
Navigationという項目を探し、その中にあるNavMeshAgentを押して追加します。
10.PNG

すると、新しくNavMeshAgentが追加されます。
11.PNG

では次にスクリプトを記入します。
先ほどと同様にEnemyを選択してInspectorを開き、AddComponentからNewScriptを選択します。
名前はNavMeshTestにしてCreateAndAddを押します。
作ったスクリプトを開いたら以下のコードを記入します。
最初から記入されているものに対して追加してある部分はコメントでわかりやすくしています                 。

NavMeshTest.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.AI;                   //☆追加

public class NavMeshTest : MonoBehaviour
{
    public NavMeshAgent agent;          //☆追加
    public GameObject target;           //☆追加  目的地

    // Start is called before the first frame update
    void Start()
    {

    }

    // Update is called once per frame
    void Update()
    {
        //目的地を設定してあげる
        agent.SetDestination(target.transform.position);//☆追加
    }
}

すると、EnemyについているNavMeshTestに以下の項目が新しく追加されます。
キャプチャ.PNG

では、このEnemyの移動先である目的地を作ります。
Create→3DObject→Cubeで四角形を出してあげてScaleをいじってあげます。Positionは床の中ならどこでも大丈夫です。(Yは0が好ましい・・・)
キャプチャ.PNG

色が見にくいので先ほど作ったマテリアルでも適応させてあげましょう。
すると、このような感じになります。
キャプチャ.PNG

最後に、EnemyについているNavMeshTestのNoneとなっている場所にEnemyとCubeを入れてあげます。
矢印を参考に選択しながら入れてみてください。

14.png

あとは再生ボタンを押すとEnemyが目的地となるCubeへ向かっていきます。

終わり

細かく書きすぎてわかりづらくなってしまった部分もありますが、慣れてしまえば実装も簡単になります。
別の機会があったら今度は索敵してくれるEnemyを作っていきたい・・・です。

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

UnityでxR有効時のカメラのFOVが固定されるのを防止する

現象

Unity 2018.2.21 で、XRSettings.showDeviceView = falseを用いて、三人称視点で撮影するカメラを置いたときにFOVが固定されてしまう現象に遭遇。

原因

VR-HMDによってFOV値が変わるので、各HMD向けに最適化されたFOV値をカメラに上書きするため。
https://docs.unity3d.com/ja/2018.2/ScriptReference/Camera-fieldOfView.html

対策

当該カメラコンポーネントにRenderTextureを割り当てれば、FOVを勝手に設定されなくなる。

RenderTextureが割り当てられておらず、Displayへ表示する設定になっているとVRカメラとして認識されてしまう模様。
英語・日本語記事共に当該現象について言及している記事が見つからず、地味にハマったのでメモを残しておきます。

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

【Unity】CSVを読み込む

CSVを読み込むには、行を , でデータを区切って読み取っていくのが最も簡単な方法ですが、
データの中に , や改行が入ると途端にめんどくなります。
そこで、CSVHelper を用いてCSVを読み込みます。
pythonならcsvモジュールで一発なんですけどね...

環境

  • macOS High Sierra
  • Unity 2019.1.8f1

準備

CSVHepler を準備する

NuGet Gallery _ CsvHelper から最新版のバージョンをクリックし、右の Download package をクリックしすると、
csvhelper.XX.X.X.nupkg というファイルがダウンロードされます。

そのファイルを右クリックし、 このアプリケーションで開く > その他... から /System/Library/CoreServices/Applications 内の アーカイブユーティリティ で解凍します。

次に csvhelper.12.1.2/lib/netstandard2.0 内の CsvHelper.dll をUnityのプロジェクトの中の Assets フォルダ内に入れます。

準備はここまで。

CSVを読み込む

ここでは例として、CSVでキャラクターのステータスの管理をしてそれを読み込みます。
用意したCSVは以下です。

Name, Hp, Atk, Def
player, 250, 250, 200
slime, 510, 180, 140

キャラクターのステータスを管理するクラスを作ります。

Character.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace CharacterController
{
    public class Character
    {
        public string Name { get; set; }
        public int Hp { get; set; }
        public int Atk { get; set; }
        public int Def { get; set; }
    }
}

CSVを読み取って、データを読み取ります。

ShowStatus.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class ShowStatus : MonoBehaviour
{
    void Start() {
        string path = @"";  // CSVファイルのパス
        var csv = Resources.Load(path) as TextAsset;
        var sr = new System.IO.StringReader(csv.text);
        var cr = new CsvHelper.CsvReader(sr);
        cr.Configuration.RegisterClassMap<CharacterMapper>();
        var records = cr.GetRecords<CharacterController.Character>();

        foreach (var record in records) {
            Debug.Log($"{record.Name}, {record.Hp}, {record.Atk}, {record.Def}");
        }
    }
}

public class CharacterMapper : CsvHelper.Configuration.ClassMap<CharacterController.Character>
{
    public CharacterMapper()
    {
        Map(x => x.Name).Index(0);
        Map(x => x.Hp).Index(1);
        Map(x => x.Atk).Index(2);
        Map(x => x.Def).Index(3);
    }
}

CharacterMapper クラスで、CSVの各フィールドが Character クラスのどのプロパティに当たるのかを指定します。

実行

適当なゲームオブジェクトにこのスクリプトをアタッチして実行してみます。

スクリーンショット 2019-10-02 12.43.32.png

先ほどのCSVと見比べて、問題なく出力できているのが確認できました。

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

[Unity] Spriteを範囲指定付きテクスチャとして使う

経緯

texture_animation.gif
一枚の画像に異なるパーツの画像が入った画像の範囲を指定してテクスチャとして使用したい。どうせならUnity上で SpriteEditor を使って視覚的に範囲指定できると便利。
つまりSpriteをスプライト以外のテクスチャとしても使えれば便利

以前書いた記事 【Unity】 クォータービューのドット絵に深度バッファを適用する でスプライトを3Dモデルに投影する手法を試しましたが、この時 Texture には Sprite 用のものではなく通常のタイプを使っていました。今回は、スプライトのままで3Dオブジェクトに貼り付ける方法を試してみました。

その過程で、通常の3Dモデル用マテリアルのテクスチャにスプライトを使う方法もわかったのでまとめてみます。

Sprite から Texture 範囲を取得(マテリアル汎用)

ググってみたら、わざわざピクセルを書き出す例があったけど、そんな面倒なことしなくても、今回の目的のためには Sprite.textureSprite.textureRect がわかれば十分でした。

マテリアルのテクスチャに範囲を設定する

スプライトの元のテクスチャと範囲矩形がわかるので、それをマテリアルに設定してやります。
Material.mainTextureでテクスチャをセットし、Material.mainTextureOffsetMaterial.mainTextureScaleで範囲指定します。

SpriteAnime.cs
        var renderer = GetComponent<MeshRenderer>();
        var material = renderer.material;
        var texSize = new Vector2(sprite.texture.width, sprite.texture.height);
        var rect = sprite.textureRect;
        material.mainTexture = sprite.texture;
        material.mainTextureOffset = new Vector2(rect.x/ texSize.x, rect.y / texSize.y);
        material.mainTextureScale = new Vector2(rect.width / texSize.x, rect.height / texSize.y);

公式ドキュメントにも書いてありますが、上で使用した Material のプロパティはシェーダーの _MainTex を置き換えます。最後の三行はこう書いても同じです。

        material.SetTexture("_MainTex",sprite.texture);
        material.SetTextureOffset("_MainTex", new Vector2(rect.x, rect.y));
        material.SetTextureScale("_MainTex", new Vector2(1 / rect.width, 1 / rect.height));

メインテクスチャ以外にスプライトを使いたい場合は、上記のように名前指定で置き換えられるでしょう。

ついでにスプライトアニメーションやってみた

ただスプライトを貼るだけでは芸がないので、スプライトの配列を設定してアニメーションができるようにしてみました。

SpriteAnime.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class SpriteAnime : MonoBehaviour
{
    [SerializeField, Header("アニメーションリスト")]
    private Sprite[] sprites = new Sprite[0];

    [SerializeField, Header("アニメーション速度"),Range(1,100)]
    private float speed = 50f;

    // Update is called once per frame
    void Update()
    {
        if (sprites.Length < 1) return;

        var index = (int)Mathf.Repeat(Time.frameCount * speed / 100, sprites.Length);
        var renderer = GetComponent<MeshRenderer>();
        var material = renderer.material;

        var sprite = sprites[index];
        var texSize = new Vector2(sprite.texture.width, sprite.texture.height);
        var rect = sprite.textureRect;
        material.mainTexture = sprite.texture;
        material.mainTextureOffset = new Vector2(rect.x/ texSize.x, rect.y / texSize.y);
        material.mainTextureScale = new Vector2(rect.width / texSize.x, rect.height / texSize.y);
        material.SetTexture("_MainTex",sprite.texture);
        material.SetTextureOffset("_MainTex", new Vector2(rect.x, rect.y));
        material.SetTextureScale("_MainTex", new Vector2(1 / rect.width, 1 / rect.height));

        renderer.material = material;
    }
}

上記をコンポーネントとして適当なゲームオブジェクトに追加します。
spriteAnime.jpgspriteeditor.jpg

結果

texture_animation.gif

Shedクラスで使う

一般的なゲームオブジェクトに対する設定方法がわかったので、今度は Shed クラス(以前の記事で作ったドット絵を直方体メッシュに正射影するクラスです)でやってみます。
もともとクラス内で mesh に UV座標を設定していたので、そこを変えるだけでよいです。
以下は関連する部分だけ抜き出したものです。完全なソースはこちら(GitHub

Shed4Sprite.cs
    public Sprite sprite;

    // ドット絵を保持するマテリアル
    public Material material;

    // マテリアルのメインテクスチャサイズ
    private Vector2Int texSize;

    // Spriteのテクスチャ領域
    private RectInt spriteRect;
    void Start()
    {
        if (!needRestruct) return;
        needRestruct = true;
        heplMessage = null;

        if (fieldsNotReady()) return;
        spriteRect = GetSpriteRect();
        texSize = GetTextureSize();

        Mesh mesh = InitializeCube();
        var newMaterial = Instantiate(material);
        newMaterial.SetTexture("_MainTex", sprite.texture);
        GetComponent<MeshFilter>().sharedMesh = mesh;
        GetComponent<MeshRenderer>().material = newMaterial;
    }

    /// <summary>
    /// テクスチャサイズを取得
    /// </summary>
    /// <returns></returns>
    private Vector2Int GetTextureSize()
    {
        var tex = sprite.texture;

        return new Vector2Int(tex.width, tex.height);
    }

    /// <summary>
    /// Spriteのテクスチャ範囲を取得
    /// </summary>
    /// <returns></returns>
    private RectInt GetSpriteRect()
    {
        var rect = sprite.textureRect;

        return new RectInt((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
    }
    /// <summary>
    /// pivotからの相対位置をUV座標に変換する
    /// </summary>
    /// <param name="offestX"></param>
    /// <param name="offsetY"></param>
    /// <returns></returns>
    private Vector2 ToUV(float offestX, float offsetY)
    {
        var x = pivot.x + offestX + spriteRect.x;
        var y = pivot.y + offsetY + spriteRect.y;
        var pos = new Vector2( x / texSize.x, y / texSize.y);
        //Debug.LogFormat("ToUV:({0},{1})",pos.x, pos.y);
        return pos;
    }

疑似3Dサイズを自動計算させよう

Shed クラスはドット絵の疑似3Dサイズを指定するようになっていますが、スプライトの画像サイズが無駄な余白を含んでいないと仮定すれば、pivot.xと画像サイズから以下のように導き出せます。

       var width = size.x + size.z;
       var height = size.y + width / 2);

せっかく SpriteEditor で視覚的に範囲指定できるので、計算で求められるものは計算して、手入力しなければならないパラメータは極力減らしたいですね。また関連部分だけの抜き出しですが、以下のようにしてみました。

Shed4Sprite.cs
    [SerializeField, Header("スプライトとpivot.xからsize自動計算")]
    public bool autoSizeAdjust = true;

    // マテリアルのメインテクスチャサイズ
    private Vector2Int texSize;

    // Spriteのテクスチャ領域
    private RectInt spriteRect;

    // Inspector 表示用の警告メッセージ
    public string heplMessage;

    private void doAutoSizeAdjust()
    {
        Debug.LogFormat("Texture size:({0},{1})", texSize.x, texSize.y);
        Debug.LogFormat("Sprite rect:({0},{1})-({2},{3})", spriteRect.x, spriteRect.y, spriteRect.width, spriteRect.height);
        pivot.y = 0;
        pivot.x = Mathf.Clamp(pivot.x, 0, spriteRect.width);

        size.z = pivot.x;
        size.x = spriteRect.width - pivot.x;
        size.y = spriteRect.height - spriteRect.width / 2;

        if (size.y < 0)
        {
            heplMessage = "スプライトの高さが足りません。最低でも横幅の半分以上必要です。";
            size.y = 0;
        }
    }

    void Start()
    {
        if (!needRestruct) return;
        needRestruct = true;
        heplMessage = null;

        if (fieldsNotReady()) return;
        spriteRect = GetSpriteRect();
        texSize = GetTextureSize();

        if (autoSizeAdjust)
        {
            doAutoSizeAdjust();
        }
        else if (!verifySize())
        {
            heplMessage = "スプライトのサイズは指定の3Dサイズに必要な大きさがありません";
        }

        //... 中略 ... //
    }

    private bool verifySize()
    {
        var width = size.x + size.z;
        if (spriteRect.width < width) return false;
        return spriteRect.height >= size.y + width / 2;
    }

プレビューにこだわる

これで、面倒な座標入力は pivot.x だけになりました。ここまで来たらこれも WYSIWYG にしたい! エディタ拡張でプレビューをカスタマイズして、pivot 位置を視覚的に確認できるようにしてみました。

Shed4SpriteEditor.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;
using UnityEngine.SceneManagement;
using UnityEditor.SceneManagement;

[CustomEditor(typeof(Shed4Sprite))]
public class Shed4SpriteEditor : Editor
{
    Shed4Sprite shed = null;
    SerializedProperty sprite;
    SerializedProperty material;
    SerializedProperty pivot;
    SerializedProperty size;
    SerializedProperty autoAdjust;

    void OnEnable()
    {
        shed = target as Shed4Sprite;
        sprite = serializedObject.FindProperty("sprite");
        material = serializedObject.FindProperty("material");
        pivot = serializedObject.FindProperty("pivot");
        size = serializedObject.FindProperty("size");
        autoAdjust = serializedObject.FindProperty("autoSizeAdjust");
    }

    public override void OnInspectorGUI()
    {
        // シリアライズオブジェクトの更新
        serializedObject.Update();

        EditorGUI.BeginDisabledGroup(true);
        EditorGUILayout.ObjectField("Script", MonoScript.FromMonoBehaviour((MonoBehaviour)target), typeof(MonoScript), false);
        EditorGUI.EndDisabledGroup();

        EditorGUI.BeginChangeCheck();

        EditorGUILayout.PropertyField(pivot);
        EditorGUILayout.PropertyField(sprite);
        EditorGUILayout.PropertyField(material);

        EditorGUILayout.PropertyField(autoAdjust);
        if (autoAdjust.boolValue)
        {
            EditorGUI.BeginDisabledGroup(true);
            EditorGUILayout.PropertyField(size);
            EditorGUI.EndDisabledGroup();
        }
        else
        {
            EditorGUILayout.PropertyField(size);
        }
        // シリアライズオブジェクトのプロパティの変更を更新
        serializedObject.ApplyModifiedProperties();

        string help = shed.heplMessage;
        if (help != null && help.Length > 0)
        {
            EditorGUILayout.HelpBox(help, MessageType.Warning);
        }

        if (EditorGUI.EndChangeCheck())
        {
            shed.UpdateMesh();
        }
    }

    // プレビューウィンドウを表示するかどうか
    public override bool HasPreviewGUI()
    {
        return true;
    }

    private bool ZoomAroundPivot
    {
        get { return shed.zoomAroundPivot; }
        set { shed.zoomAroundPivot = value; }
    }

    // プレビューウィンドウのヘッダーバーをカスタムする関数
    public override void OnPreviewSettings()
    {
        ZoomAroundPivot = GUILayout.Toggle(ZoomAroundPivot, "zoom pivot");
    }

    // プレビューウィンドウで描画させたいものはここで書く
    public override void OnPreviewGUI(Rect r, GUIStyle background)
    {
        if (ZoomAroundPivot) _drawAroundPivot(r);
        else _drawEntire(r);
    }

    private void _drawEntire(Rect r)
    {
        Vector2 size2D = shed.Size2D;

        const int border = 2;

        var scale = Mathf.Min((r.width - border * 2)/ size2D.x , (r.height - border * 2)/ size2D.y);
        var centerPos = new Vector2(r.x + r.width / 2, r.y + r.height / 2);
        var drawSize = size2D * scale;
        var size3D = ((Vector3)shed.size) * scale;

        var offsetPos = new Vector2(centerPos.x - border - drawSize.x / 2, centerPos.y -border - drawSize.y / 2);

        // 3D投影サイズ境界のx,zを赤と緑の外殻線で描く
        var maxPos = new Vector2(offsetPos.x + drawSize.x + border * 2, offsetPos.y + drawSize.y + border * 2);
        Handles.DrawSolidRectangleWithOutline(new Rect(offsetPos.x, offsetPos.y, size3D.x + border, border - 1), Color.red, Color.red);
        Handles.DrawSolidRectangleWithOutline(new Rect(offsetPos.x, offsetPos.y, border - 1, size3D.x / 2 + border), Color.red, Color.red);
        Handles.DrawSolidRectangleWithOutline(new Rect(offsetPos.x, maxPos.y, size3D.z + border, border - 1), Color.green, Color.green);
        Handles.DrawSolidRectangleWithOutline(new Rect(offsetPos.x, maxPos.y, border - 1, -size3D.z / 2), Color.green, Color.green);

        Handles.DrawSolidRectangleWithOutline(new Rect(maxPos.x, maxPos.y, -size3D.x - border, border - 1), Color.red, Color.red);
        Handles.DrawSolidRectangleWithOutline(new Rect(maxPos.x, maxPos.y, border - 1, -size3D.x / 2 - border), Color.red, Color.red);
        Handles.DrawSolidRectangleWithOutline(new Rect(maxPos.x, offsetPos.y, -size3D.z - border, border - 1), Color.green, Color.green);
        Handles.DrawSolidRectangleWithOutline(new Rect(maxPos.x, offsetPos.y, border - 1, size3D.z / 2 + border), Color.green, Color.green);

        var texture = shed.sprite.texture;
        var texRect = shed.SpriteRect;
        var texSize = new Vector2(texture.width, texture.height);
        //Debug.LogFormat($"({texSize.x},{texSize.y}) , ({texRect.xMin},{texRect.yMin}) -({texRect.xMax}, {texRect.yMax})");

        // 中央にスプライト領域を描画
        var drawRect = new Rect(offsetPos.x + border, offsetPos.y + border, drawSize.x, drawSize.y);
        Rect texCoord = new Rect(texRect.x / texSize.x, texRect.y / texSize.y, texRect.width / texSize.x, texRect.height / texSize.y);
        GUI.DrawTextureWithTexCoords(drawRect, texture, texCoord);
    }

    private void _drawAroundPivot(Rect r)
    {
        Vector2 size2D = shed.Size2D;

        const int border = 2;
        const int zoom = 3;

        var scale = Mathf.Min(r.width / size2D.x, (r.height - border) / size2D.y);
        scale = Mathf.Max(3, scale);
        var hfWidth = r.width / 2;
        var hfHeight = r.height / 2;

        // 3D投影サイズ境界のx,zを赤と緑の外殻線で描く
        Handles.DrawSolidRectangleWithOutline(new Rect(r.x, r.yMax, hfWidth, border - 1), Color.green, Color.green);
        Handles.DrawSolidRectangleWithOutline(new Rect(r.x + hfWidth, r.yMax, hfWidth, border - 1), Color.red, Color.red);

        var texture = shed.sprite.texture;
        var texRect = shed.SpriteRect;
        var texSize = new Vector2(texture.width, texture.height);
        //Debug.LogFormat($"({texSize.x},{texSize.y}) , ({texRect.xMin},{texRect.yMin}) -({texRect.xMax}, {texRect.yMax})");
        Vector2 pivot = shed.pivot;
        var clipSize = new Vector2(hfWidth / zoom, (r.height - border) / zoom);
        Rect texCoord = new Rect(
            (texRect.x + pivot.x - clipSize.x) / texSize.x, texRect.y / texSize.y,
            clipSize.x * 2 / texSize.x, clipSize.y / texSize.y);


        // 中央にスプライト領域を描画
        var drawRect = new Rect(r.x, r.y, r.width, r.height - border);
        GUI.DrawTextureWithTexCoords(drawRect, texture, texCoord);
    }
}

結果

pivot を変更すると連動してプレビューが動いて、赤と緑の枠がそれぞれ3D投影サイズの x,z の範囲を示すようにサムネイル画像外縁に表示します。さらに 'zoom pivot' にチェックすると pivot 周辺が拡大表示されてピクセル単位での位置あわせが容易にできるようにしました。  
inspector-preview.gif

参考記事

エディタ拡張については下記を参考にさせていただきました。
http://baba-s.hatenablog.com/entry/2019/04/10/181000
https://techblog.kayac.com/unity_advent_calendar_2018_16
https://qiita.com/kyourikey/items/7a5f693d1fe17bde5387

まとめ

  • スプライトの texture と textureRect で元のテクスチャと引用範囲を取得できた
  • マテリアルの mainTexture, mainTextureOffset, mainTextureScale で範囲指定付きでテクスチャを設定できた
  • スプライトを使った3Dモデルのテクスチャアニメーションができた
  • ドット絵正射影用のShedクラスもUV計算時に範囲計算を反映することでスプライトを使用できるようにした
  • Shedクラスの3D投影サイズをスプライト領域から自動計算するようにした
  • エディタ拡張でShedクラスのプレビューに3D投影サイズがわかりやすく表示できた
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Unity】Xcode11でiOSシミュレータ用のビルドをすると"Duplicate interface definition for class 'CAMetalLayer'"エラーになる

はじめに

いつの間にかiOSシミュレータ用のビルドができなくなって困り、いろいろ調べたメモ。

経緯と症状

  1. いつの間にかXcodeが起動できなくなった(勝手にXcodeがアップデートされた?)
    スクリーンショット 2019-09-24 16.08.12.png

  2. OSをアップデート、Xcode11が使えるように

  3. シミュレータ用ビルドが以下のエラーで通らなくなった

Unity-iPhone > Classes > Unity > UnityMetalSupport.h > CAMetalLayer
Duplicate interface definition for class 'CAMetalLayer'
Property has a previous declaration
Property has a previous declaration
Property has a previous declaration
..

スクリーンショット 2019-09-28 14.54.20.png

Player SettingsGraphics APIsの設定をガチャガチャいじってみても解決せず。

再現環境

  • Unity
    • 2018.2.21f1
    • 2017.3.1f1
  • Xcode11
  • Target SDK:Sumulator SDK

原因

Xcode - New Features
・CAMetalLayer is now available in Simulator. (45101325)

新しいXcodeではシミュレータでも CAMetalLayer というのが使えるようになったらしく、これまでUnityがシミュレータ用ビルド向けにゴニョゴニョしていた部分とコンフリクトを起こしているみたいです。

この問題は以下のIssue Trackerにも登録されていました。

Unityのリリースノートをたどったところ、以下のバージョンで対応済みのようです。

iOS: Fixed simulator build on xcode11 beta (metal headers were added) - now we use headers if available instead of forcibly declaring symbols (1163876, 1170516)

2017.x系については最近のリリースノートを探しても見当たらなかったので未対応だと思われます(LTSのサポート切れ?)。

対処方法

使用するUnityのバージョンを上げる

先に述べたとおり2019.2.3f1,2018.4.9f1以降では対応済みなので、素直にUnityのバージョンを上げましょう。

ただし、2018.4.9-10は不具合があるとかでアップデートするかどうかは悩みどころ。

古いXcodeを使う

とりあえずXcode10.3なら問題なくビルドできたので、Unityをアップデートできない人は古いXcodeを使うしかないでしょう。iOS13の対応や今後のこともあるのであまりおすすめはしません。

Xcodeが複数インストールしてあるとBuild Settingsからビルドに使用するXcodeのバージョンを選択できます。
Oct-02-2019 10-17-42.gif

ちなみに

Xcode11ではApplication Loaderが同梱されなくなったそうです。

ビルドをApplication LoaderでApp Store ConnectにアップロードするJenkinsおじさんとかがいたら死ぬので対応しましょう。

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

UnityInjectorのHelloWorld

COM3D2でお馴染みUnityInjector。
適当にDLL形式のプラグインを落としてきてフォルダに突っ込めば、標準機能ではできないあんなことやこんなことができる優れものです。しかし、当然のことながら他人の作ったプラグインだけでは辛抱ならないのが男の子です。というわけで、パパっと作ってしまいたいところですが、アングラなものなこともあってかあまり情報がありません。エンドユーザー向けの情報は結構Wikiとかブログとかにまとまってるんですけどね…

ということで、COM3D2でHelloWorldするUnityInjectorプラグインのチュートリアルを書き残したいと思います。COM3D2以外もパスを変えれば、多分同じ手順で行けると思います。

環境構築

dotnetがビルドできるようになればなんでもいいのですが、手っ取り早くかつ最低限の機能はそろっている.NET Coreで開発を行いたいと思います。

.NET Coreはコマンドラインツールでdotnetのプロジェクトビルドをすることができます。GUIがお好みの方はVS入れてください。同じDLLを参照させて、同じコードを書けば同じ結果になると思います。

まずは以下のリンク(Microsoft)から、.NET Core SDKをDLしインストールします。
https://dotnet.microsoft.com/download

するとdotnetコマンドが使えるようになります。cmdかPowerShellで叩いてみてください。うまくいかない場合はパスが通っていない可能性があるのでパスを通しましょう。

次に、COM3D2+UnityInjectorがインストールされていない場合はインストールしてください。UnityInjectorはSybarisを入れればフォルダ内にUnityInjector.dllがあるはずです。

プロジェクト生成

適当なプロジェクトフォルダを作ります。ここではCom3d2HelloWorldとします。そしてdotnetコマンドでプロジェクトを生成します。プロジェクトタイプはclasslib(直接実行しないライブラリのプロジェクト)

mkdir Com3d2HelloWorld
cd Com3d2HelloWorld
dotnet classlib

すると、Com3d2HelloWorldフォルダ内にCom3d2HelloWorld.csproj及び、Class1.csというファイルが生成されていることが確認できると思います。

参照の追加

プラグインがUnityInjector、UnityEngine、そしてCOM3D2のクラスを呼び出すために、プラグインプロジェクトに参照を追加します。尤も、今回はHelloWorldをするだけなのでCOM3D2のクラスは使用しませんが。csprojに追記します。

Com3d2HelloWorld.csproj
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
  </PropertyGroup>

  <!-- ここから追記 -->
  <ItemGroup>
    <Reference Include="UnityInjector">
      <HintPath>C:\KISS\COM3D2\Sybaris\UnityInjector.dll</HintPath>
    </Reference>
    <Reference Include="UnityEngine">
      <HintPath>C:\KISS\COM3D2\COM3D2x64_Data\Managed\UnityEngine.dll</HintPath>
    </Reference>
    <Reference Include="Assembly-CSharp"><!-- COM3D2クラスが詰まってるDLL -->
      <HintPath>C:\KISS\COM3D2\COM3D2x64_Data\Managed\Assembly-CSharp.dll</HintPath>
    </Reference>
  </ItemGroup>
  <!-- ここまで-->

</Project>

パスは各自の環境に合わせてください。

csprojを反映させます。(要らないかも)

dotnet restore

コーディング

Class1.csの方にコードを書いていきます。ファイル名、クラス名に制約はありません。(衝突しなければ)
Class1というのはあまりにもなので、Com3d2HelloWorld.csにリネームしておきます。そして、次のようなコードを書きます。

Com3d2HelloWorld.cs
using UnityEngine;
using UnityInjector;
using UnityInjector.Attributes;

namespace Com3d2HelloWorld
{
    /// <summary>
    /// これはMonoBehaviourで、UnityInjectorによりInstansiateされてシーンに放り出されます。
    /// 実際にゲーム内と相互作用をするにはObject.FindObjectOfTypeで所望のGemeObject(メイドさん等)を捕まえて
    /// そこからGameObject.GetComponentでコンポーネントを捕まえてメンバを書き換えたり実行するというフローになります。
    /// PluginFilterは対象とするExeです。この場合64bit版と32bit版を指定しています。ほかにもVR版などもあります。
    /// </summary>
    [PluginFilter("COM3D2x64"), PluginFilter("CM3D2x86"),PluginName("Com3d2HelloWorld"), PluginVersion("0.0.0.0")]
    public class Com3d2HelloWorld : PluginBase
    {
        void OnGUI()
        {
            // とりあえず画面にHello world!出しておく
            // MonoBehaviourなのでOnGUIやUpdate等、Unityのコールバック関数が自由に使えます
            GUI.Label(new Rect(100, 100, 500, 100), "Hello world! [8846]");
        }
    }
}

コンパイル&デプロイ

以下のコマンドを実行してビルドします。

dotnet build

すると、Com3d2HelloWorld\bin\Debug\Com3d2HelloWorld.dllが出来上がっている筈です。これをCOM3D2のプラグインとして配置してあげればOKです。おそらくSybaris\UnityInjector\配下ですね。

確認

COM3D2を起動します。コンソールに次のように表示されていることを確認します。

Loading Assembly: 'C:\KISS\COM3D2\Sybaris\UnityInjector\Com3d2HelloWorld.dll'

ゲーム画面を見ると…

コメント 2019-10-02 032412.png

まとめ

要するに

  • UnityEngine.dll
  • UnityInjector.dll
  • Assembly-CSharp.dll

を参照し

  • PluginFilterAttribute
  • PluginNameAttribute
  • PluginVersionAttribute

のついたPluginBase派生のMonoBehaviourなクラスを含むDLLを作れば大丈夫です。

参考になりそうなの

https://umaiumeunion.github.io/guides_unityinjector/03_writing_plugins
多分これがすべて(書き始めて気づいた)

https://gist.github.com/neguse11/1951c3625ee7aa153a2a

https://github.com/asyetriec/COM3D2.AddYotogiSlider.Plugin/blob/master/COM3D2.AddYotogiSlider.Plugin/AddYotogiSlider.cs

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