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

UnityシーンのヒエラルキーをYAMLから復元する

この記事の内容

Unityの環境: 2019.4.12
Unityのシーンファイル(.unity)はYAMLで記述されています。そこで、.unityファイルを直接読み込むことでヒエラルキーを復元します。
ただし、プレハブを含んだものはかなり大変なので、プレハブをヒエラルキーに含まないものとします。
(シーンのファイルだけでヒエラルキーが完結するもの。)
例えば、以下の簡単なシーン(SampleScene.unity)の場合、
2020-10-19_20h59_03.png
2020-10-19_20h59_30.png
↓こんな感じになります。
2020-10-19_21h00_44.png

Unityの内部の処理が分かった気になれると思います。
結果だけ欲しい人は、コード名が記載されているコードだけ見て他はスキップしてください。
(途中の説明で使うコード部分はコード名を記載してません。)

UnityでのYAML

準備

まずYAMLを読む準備をします。UnityでのYAMLはYamlDotNet for Unityアセットを用います。
ただし、自分が入れたときはアセットのnamespaceが認識されませんでした。
Plugins/YamlDotNet/YamlDotNet.asmdefのautoReferencedをtrueにして再読み込みするとうまくいきました。
(プラグインのレビューのDrewProTagさんのコメントのおかげで解決した)
2020-10-19_21h10_51.png

YAMLの構文

Unityで使われているYAMLの構文を簡単におさらいします。(偉そうにいっているが今回初めて勉強した。)
まず、上で例に挙げたSampleScene.unityの最初の方を最初の方だけお見せします。

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!29 &1
OcclusionCullingSettings:
  m_ObjectHideFlags: 0
  serializedVersion: 2
  m_OcclusionBakeSettings:
    smallestOccluder: 5
    smallestHole: 0.25
    backfaceThreshold: 100
  m_SceneGUID: 00000000000000000000000000000000
  m_OcclusionCullingData: {fileID: 0}
--- !u!104 &2
RenderSettings:
//省略
--- !u!157 &3
LightmapSettings:
  m_ObjectHideFlags: 0
  serializedVersion: 11
  m_GIWorkflowMode: 1
  m_GISettings:
    serializedVersion: 2
    m_BounceScale: 1
    m_IndirectOutputScale: 1
    m_AlbedoBoost: 1
    m_EnvironmentLightingMode: 0
    m_EnableBakedLightmaps: 1
    m_EnableRealtimeLightmaps: 0
  m_LightmapEditorSettings:
    serializedVersion: 12
    m_Resolution: 2
    m_BakeResolution: 40
    m_AtlasSize: 2048
    m_AO: 0
    m_AOMaxDistance: 1
    m_CompAOExponent: 1
    m_CompAOExponentDirect: 0
    m_ExtractAmbientOcclusion: 0
    m_Padding: 2
    m_LightmapParameters: {fileID: 0}
    m_LightmapsBakeMode: 1
    m_TextureCompression: 1
    m_FinalGather: 0
    m_FinalGatherFiltering: 1
    m_FinalGatherRayCount: 256
    m_ReflectionCompression: 2
    m_MixedBakeMode: 2
    m_BakeBackend: 1
    m_PVRSampling: 1
    m_PVRDirectSampleCount: 32
    m_PVRSampleCount: 500
    m_PVRBounces: 2
    m_PVREnvironmentSampleCount: 500
    m_PVREnvironmentReferencePointCount: 2048
    m_PVRFilteringMode: 2
    m_PVRDenoiserTypeDirect: 0
    m_PVRDenoiserTypeIndirect: 0
    m_PVRDenoiserTypeAO: 0
    m_PVRFilterTypeDirect: 0
    m_PVRFilterTypeIndirect: 0
    m_PVRFilterTypeAO: 0
    m_PVREnvironmentMIS: 0
    m_PVRCulling: 1
    m_PVRFilteringGaussRadiusDirect: 1
    m_PVRFilteringGaussRadiusIndirect: 5
    m_PVRFilteringGaussRadiusAO: 2
    m_PVRFilteringAtrousPositionSigmaDirect: 0.5
    m_PVRFilteringAtrousPositionSigmaIndirect: 2
    m_PVRFilteringAtrousPositionSigmaAO: 1
    m_ExportTrainingData: 0
    m_TrainingDataDestination: TrainingData
    m_LightProbeSampleCountMultiplier: 4
  m_LightingDataAsset: {fileID: 112000000, guid: 4f5eb19331614e343b2ca2383492174c,
    type: 2}
  m_UseShadowmask: 1
--- !u!196 &4
NavMeshSettings:
//省略
--- !u!1 &495889487
GameObject:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  serializedVersion: 6
  m_Component:
  - component: {fileID: 495889491}
  - component: {fileID: 495889490}
  - component: {fileID: 495889489}
  - component: {fileID: 495889488}
  m_Layer: 0
  m_Name: Capsule
  m_TagString: Untagged
  m_Icon: {fileID: 0}
  m_NavMeshLayer: 0
  m_StaticEditorFlags: 0
  m_IsActive: 1
--- !u!136 &495889488
CapsuleCollider:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 495889487}
  m_Material: {fileID: 0}
  m_IsTrigger: 0
  m_Enabled: 1
  m_Radius: 0.5
  m_Height: 2
  m_Direction: 1
  m_Center: {x: 0, y: 0, z: 0}
--- !u!23 &495889489
MeshRenderer:
//省略
--- !u!23 &495889489
MeshFilter:
//省略
--- !u!4 &495889491
Transform:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 495889487}
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
  m_LocalPosition: {x: 1.05792, y: -0.8924775, z: 0.5581703}
  m_LocalScale: {x: 1, y: 1, z: 1}
  m_Children: []
  m_Father: {fileID: 0}
  m_RootOrder: 2
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
//もっと続く

重要な部分を順番に説明します。

まず、一つのYAMLファイルは複数のdocumentを含むことができます。
ハイフン三つ "---" がドキュメントの始まりを表しており、documentごとに取り出して処理をしていきます。

documentを例を用いて説明します。上でお見せしたYAMLの最後のドキュメントは

--- !u!4 &495889491

と書かれています。
まず !u!4 の部分はタグを表します。結論を言えば、YAMLの最初のところで

%TAG !u! tag:unity3d.com,2011:

と書かれており、これは !u! で tag:unity3d.com,2011: を表す、という意味なので、
!u!4 は tag:unity3d.com,2011:4を表します。重要なのは !u! の後の数字で、これはUnityのクラスの種類を表します。
どの数字が何を表すかはUnityの公式ページ(YAML クラス ID リファレンス)に書かれています。
4はTransformを表しており。後で使うGameObjectは1で表されます。

次に !u!4 の後の &495889491 の数字はFileIDを表しています。
FileIDとは一つのアセット内での参照で使用されるドキュメントのIDのことです。
GUIDは聞いたことがあるかもしれませんが、GUIDはアセット自体の参照に使うIDです。
なので、一つのファイル内ではFileIDでdocumentを一意に決めることができますが、他のアセットのドキュメントを参照したいときは、アセットを表すGUIDとそのアセットないでのFileIDを指定する必要があります。
シーンにプレハブが含まれている場合には、(当たり前ですが)シーンの外にあるプレハブを使うので、プレハブGUIDを使って何を参照しているかたどる必要があります。
今回はGUIDを使わずFileIDだけで済むシーンを扱います。

さて、YAMLでは、連想配列つまりdictionaryをコロン:で表します。例えば、

A: 0
B: 1

でkey Aに対してvalueが0、key Bに対して value が1であることを表します。一行で連想配列を表すときは、波括弧{}で表します。
例えば、

{A: 0, B: 0, C: 0}

のように書きます。次にリスト(普通の配列)はハイフン-で表します。

- 0
- 1

一行で書く場合は角括弧[]を用いて、

[0, 1]

のようにかきます。

以上でUnityのYAMLを読む上で必要な知識はだいたい説明できました。
最後のdocumentの全体をもう一度見てみましょう。

--- !u!4 &495889491
Transform:
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_GameObject: {fileID: 495889487}
  m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
  m_LocalPosition: {x: 1.05792, y: -0.8924775, z: 0.5581703}
  m_LocalScale: {x: 1, y: 1, z: 1}
  m_Children: []
  m_Father: {fileID: 0}
  m_RootOrder: 2
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}

まず、タグが4なのでこのdocumentはTransformを表していることが分かります。
documentの連想配列としてkeyがTransformだけなので、これを用いて中身のデータが取れます。(タグでTransformであることは分かるのですが冗長にできています。)

よって、Transform -> m_LocalPosition->x とkeyをたどることで、xの局所座標1.05792を得ることができます。
m_ChildrenはTransformの子Transformを表すのですが、角括弧なのでこれだけリストで格納されていることに注意してください。

YamlDotNet for Unity でデータを取り出す

以下の記事を参考にしています。
【Unity】YAMLファイルの読み込みと表示用メッセージの管理

まず、シーンのパスScenePathを取得しておいて、それを使ってdocumentの配列を取得します。

    StreamReader inputFile = new StreamReader(ScenePath, System.Text.Encoding.UTF8);
    var yamlStream = new YamlStream();
    yamlStream.Load(inputFile);
    IList<YamlDocument> documents = yamlStream.Documents;

documentのうち今回はGameObjectとTransformのみが興味あるので、以下の連想配列を用意しました。

    static Dictionary<string, string> unityClassDic = new Dictionary<string, string>() {
        {"1", "GameObject"},
        {"4", "Transform"}
    };

documentからタグとFileIDを取得するために便利staticクラスを用意しました。

public static class YamlSupport
{
    public static string ClassTag(YamlDocument document)
    {
        return document.RootNode.Tag.Split(':').Last();
    }

    public static string FileID(YamlDocument document)
    {
        return document.RootNode.Anchor;
    }
}

これを用いてすべてのdocumentを読んでいきます。

string documentClass;
//ドキュメントごとにロードしていく
foreach (var document in documents)
{
//興味があるクラスでなければスキップ
     if (!unityClassDic.TryGetValue(YamlSupport.ClassTag(document), out documentClass))
         continue;

     var fileID = YamlSupport.FileID(document);

     if (documentClass == "GameObject")
     {
             //GameObjectに対する処理
     }
     else if (documentClass == "Transform")
     {
            //Transformに対する処理
     }
}

Unityは基本的に連想配列でデータが取れるので、keyとdocumentを渡してvalue(のstring)を返す関数を使いまくります。
ただし、Transformの子Transformを保存したm_Childrenだけリストを使っているので、そのための関数も用意します。

    public static string GetValue(string key, YamlDocument document)
    {
        string[] keys = key.Split('.');

        int keyCount = keys.Length;

        YamlMappingNode mapping = (YamlMappingNode)document.RootNode;

        for (int i = 0; i < keyCount; i++)
        {
            YamlScalarNode currentNode = new YamlScalarNode(keys[i]);
            var outNode = mapping.Children[currentNode];

            if (i == keyCount - 1)
            {
                return (outNode as YamlScalarNode).ToString();
            }
            else
            {
                mapping = (YamlMappingNode)outNode;
            }
        }

        return "Error";
    }


    //リストにデータが一つ入っているものを取り出す
    //Transform:
    //    m_Children:
    //    - {fileID: 1689986398}
    //    - {fileID: 789851047}
    //の場合、GetList<"Transform.m_Children", "fileID", document)とする
    public static List<string> GetList(string key, string lastKey, YamlDocument document)
    {
        // キーをドットで分割
        string[] keys = key.Split('.');

        // キーの配列数(=ネストレベル)取得
        int keyCount = keys.Length;

        YamlMappingNode mapping = (YamlMappingNode)document.RootNode;
        YamlNode node = null;

        List<string> rtnList = new List<string>();


        for (int i = 0; i < keyCount; i++)
        {
            YamlScalarNode currentNode = new YamlScalarNode(keys[i]);
            var outNode = mapping.Children[currentNode];

            if (i == keyCount - 1)
            {
                node = outNode;
            }
            else
            {
                mapping = (YamlMappingNode)outNode;
            }
        }

        var sequence = (YamlSequenceNode)node;

        var childrenList = sequence.Children;

        foreach (var child in childrenList)
        {
            mapping = (YamlMappingNode)child;

            YamlScalarNode currentNode = new YamlScalarNode(lastKey);
            var outNode = mapping.Children[currentNode];

            rtnList.Add(((YamlScalarNode)outNode).ToString());
        }


        return rtnList;
    }

以上をまとめて、YAMLからSceneのデータを読み込んで保存する以下のクラスを作成しました。

YamlScene.cs
using System.Collections;
using System.Collections.Generic;
using System.Linq;
//using UnityEngine;
using System.IO;
using YamlDotNet.Core;
using YamlDotNet.RepresentationModel;

public class YamlScene
{
    public string SceneName { get; }
    public string ScenePath { get; }

    //今回は使わない
    string GUID { get; }

    //YamlDotNet for Unity を使用して初期化する
    public YamlScene(string scenePath)
    {
        ScenePath = scenePath;
        SceneName = scenePath.Split('/').Last();

        Load();
    }

    static Dictionary<string, string> unityClassDic = new Dictionary<string, string>() {
        {"1", "GameObject"},
        {"4", "Transform"}
    };


    //シーン中のデータ
    public List<YamlGameObject> GameObjects { get; } = new List<YamlGameObject>();

    public List<YamlTransform> Transforms { get; } = new List<YamlTransform>();

    //シーン直下のTransform。親を持たないもの。このシーンの子供と考える
    public List<YamlTransform> childTransforms = new List<YamlTransform>();

    private void Load()
    {
        StreamReader inputFile = new StreamReader(ScenePath, System.Text.Encoding.UTF8);
        var yamlStream = new YamlStream();
        yamlStream.Load(inputFile);

        IList<YamlDocument> documents = yamlStream.Documents;


        GameObjects.Clear();
        Transforms.Clear();


        string documentClass;
        //ドキュメントごとにロードしていく
        foreach (var document in documents)
        {
            //興味があるクラスでなければスキップ
            if (!unityClassDic.TryGetValue(YamlSupport.ClassTag(document), out documentClass))
                continue;

            var fileID = YamlSupport.FileID(document);

            if (documentClass == "GameObject")
            {
                string key = "GameObject.m_Name";
                string objName = YamlSupport.GetValue(key, document);
                GameObjects.Add(new YamlGameObject(objName, fileID));
            }
            else if (documentClass == "Transform")
            {
                string key = "Transform.m_GameObject.fileID";
                string objID = YamlSupport.GetValue(key, document);

                key = "Transform.m_Children";
                var children = YamlSupport.GetList(key, "fileID", document);


                key = "Transform.m_RootOrder";
                string rootOrder = YamlSupport.GetValue(key, document);

                key = "Transform.m_Father.fileID";
                string father = YamlSupport.GetValue(key, document);


                Transforms.Add(new YamlTransform(fileID, objID, rootOrder, children, father));
            }
        }

        //IDを使って参照をセット
        //Transformに参照を入れていく
        foreach (var transform in Transforms)
        {
            //IDが合うオブジェクトを探す
            foreach (var obj in GameObjects)
            {
                if (obj.FileID == transform.GameObjectID)
                {
                    transform.yamlGameObject = obj;
                    break;
                }
            }

            //Transformの子供をセットする
            foreach (var child in transform.ChildrenID)
            {
                foreach (var other in Transforms)
                {
                    if (child == other.FileID)
                    {
                        transform.children.Add(other);
                    }
                }
            }

            //親がないものはシーンの子供に入れる
            if (transform.FatherID == "0")
            {
                childTransforms.Add(transform);
            }
        }

        //並び替え
        childTransforms.Sort((a, b) => int.Parse(a.RootOrder) - int.Parse(b.RootOrder));
        foreach (var transform in Transforms)
        {
            transform.children.Sort((a, b) => int.Parse(a.RootOrder) - int.Parse(b.RootOrder));
        }
    }

}

public class YamlGameObject
{ 
    public string FileID { get; set; }
    public string ObjectName { get; set; }
    public YamlGameObject(string _name, string _id)
    {
        ObjectName = _name;
        FileID = _id;
    }

}

public class YamlTransform
{
    public string FileID { get; set; }

    public string RootOrder { get; set; }

    public string FatherID { get; set; }

    public string GameObjectID { get; set; }
    public List<string> ChildrenID { get; set; }

    public YamlTransform(string fileID, string gameObjectID, string rootOrder, List<string> childrenID, string fatherID)
    {
        FileID = fileID;

        GameObjectID = gameObjectID;

        RootOrder = rootOrder;

        ChildrenID = childrenID;

        FatherID = fatherID;
    }

    //ロードが終わってからクラスへの参照を計算する
    public YamlGameObject yamlGameObject = null;
    public List<YamlTransform> children = new List<YamlTransform>();

}


public static class YamlSupport
{
    public static string FileID(YamlDocument document)
    {
        return document.RootNode.Anchor;
    }

    public static string ClassTag(YamlDocument document)
    {
        return document.RootNode.Tag.Split(':').Last();
    }

    public static string GetValue(string key, YamlDocument document)
    {
        string[] keys = key.Split('.');

        int keyCount = keys.Length;

        YamlMappingNode mapping = (YamlMappingNode)document.RootNode;

        for (int i = 0; i < keyCount; i++)
        {
            YamlScalarNode currentNode = new YamlScalarNode(keys[i]);
            var outNode = mapping.Children[currentNode];

            if (i == keyCount - 1)
            {
                return (outNode as YamlScalarNode).ToString();
            }
            else
            {
                mapping = (YamlMappingNode)outNode;
            }
        }

        return "Error";
    }


    //リストにデータが一つ入っているものを取り出す
    //Transform:
    //    m_Children:
    //    - {fileID: 1689986398}
    //    - {fileID: 789851047}
    //の場合、GetList<"Transform.m_Children", "fileID", document)とする
    public static List<string> GetList(string key, string lastKey, YamlDocument document)
    {
        // キーをドットで分割
        string[] keys = key.Split('.');

        // キーの配列数(=ネストレベル)取得
        int keyCount = keys.Length;

        YamlMappingNode mapping = (YamlMappingNode)document.RootNode;
        YamlNode node = null;

        List<string> rtnList = new List<string>();


        for (int i = 0; i < keyCount; i++)
        {
            YamlScalarNode currentNode = new YamlScalarNode(keys[i]);
            var outNode = mapping.Children[currentNode];

            if (i == keyCount - 1)
            {
                node = outNode;
            }
            else
            {
                mapping = (YamlMappingNode)outNode;
            }
        }

        var sequence = (YamlSequenceNode)node;

        var childrenList = sequence.Children;

        foreach (var child in childrenList)
        {
            mapping = (YamlMappingNode)child;

            YamlScalarNode currentNode = new YamlScalarNode(lastKey);
            var outNode = mapping.Children[currentNode];

            rtnList.Add(((YamlScalarNode)outNode).ToString());
        }


        return rtnList;
    }
}

シーンのヒエラルキーを表示する

作ったクラスを使って、ヒエラルキーを構成し表示するEditorWindowを作りました。
簡単な確認用なので、初期化を適当にやってます。Windowを開いたままエディタを閉じたとき、開きなおすとエラーが出たりするので、実際に使う場合は作りなおしてください。

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

public class SceneViewer : EditorWindow
{
    static bool isOpen = true;
    static Dictionary<YamlTransform, bool> openDictionary;

    static YamlScene yamlScene;

    [MenuItem("MyEditor/SceneView")]
    static void Open()
    {
        yamlScene = new YamlScene(SceneManager.GetActiveScene().path);

        openDictionary = new Dictionary<YamlTransform, bool>();
        foreach (var yTransform in yamlScene.Transforms)
        {
            openDictionary.Add(yTransform, true);
        }

        GetWindow<SceneViewer>();
    }

    private void OnGUI()
    {
        isOpen = EditorGUILayout.Foldout(isOpen, yamlScene.SceneName);
        if (isOpen)
        {
            using (new EditorGUI.IndentLevelScope())
            {
                foreach (var transform in yamlScene.childTransforms)
                {
                    RecursiveGUI(transform);
                }
            }

        }
    }

    static void RecursiveGUI(YamlTransform yTransform)
    {
        bool isOpen = openDictionary[yTransform];

        if (yTransform.children.Count >= 1)
        {
            using (new GUILayout.VerticalScope())
            {
                openDictionary[yTransform] = EditorGUILayout.Foldout(isOpen, yTransform.yamlGameObject.ObjectName);
                if (isOpen)
                {
                    using (new GUILayout.HorizontalScope())
                    {
                        GUILayout.Space(20f); // horizontal indent size of 20 (pixels)

                        using (new GUILayout.VerticalScope())
                        {
                            foreach (var child in yTransform.children)
                            {
                                RecursiveGUI(child);
                            }

                        }
                    }

                }
            }
        }
        else
        {
            using (new GUILayout.HorizontalScope())
            {
                GUILayout.Space(20f);
                using (new GUILayout.VerticalScope())
                {
                    GUILayout.Label(yTransform.yamlGameObject.ObjectName);
                }
            }
        }
    }
}

あとがき

例外処理をやってなかったり、初期化をちゃんとやってないとはいえ、400行もいかずにヒエラルキーが見れるのは分かった気になれてうれしいです。
シーンの右クリックから開くようにすれば、アクティブなシーンを変更せずにヒエラルキーが確認できるようにできます。
ここまでいくと実用的だと思います。

以下に、時間があるときに気になった点を書く予定です。

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

UNITYでキャラクターを動かす(Rigitbody_velocity)

はじめに

本記事では、Unityでキャラクターを動かすまでの手順を備忘録として残す。
移動方法はいくつかあるが、今回はRigitbodyのvelocityを利用する。
手順通りに進めると、以下のようなものができあがる。

↑:前進
↓:後退
←:左回転
→:右回転
カメラがキャラクターを追従

Rigitbody.velocityによる移動

Rigitbodyのvelocity(速度)を直接書き換えることで移動する

メリット

物理演算が適用される
一定の速度で移動するので扱いやすい
単調なゲームに向いている

デメリット

質量や重力を考慮していないので挙動が非現実的
リアルな動作を追求したい場合は向かない

手順

0.前準備

Cube等で床を作り、ColliderをAdd Componentする
(Cube等をCreateした場合、Colliderはデフォルトでセットされている)

1.キャラクターを設置

キャラクターを用意する(めんどくさければとりあえずCubeでもOK)
メインカメラで見える位置に設置する

2.RigitBodyを追加

キャラクターにRigitBodyをAdd Compornentする
Use Gravity  ☑
Is Kinematic ☐
※再生してキャラクターが床をすり抜けて落ちれば成功

3.Colliderを追加

キャラクターにColliderをAdd Compornentする
キャラクターと同じくらいの大きさを設定する
Is Trigger  ☐
※再生してキャラクターが床をすり抜けなくなれば成功

4.スクリプトを追加

キャラクターに下記スクリプトをAdd Compornentする
InspectorからspeedとrotateSpeedを設定する
※再生してキャラクターを操作できれば成功

    public float speed;
    public float rotateSpeed;

    private Transform tr;
    private Rigidbody rb;
    private Vector3 charactorForward ;
    private Vector3 moveVector;
    private float vertical;
    private float horizontal;

    void Start()
    {
        tr = this.transform;
        rb = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
     //上下の入力
        vertical = Input.GetAxis("Vertical");
     //左右の入力
        horizontal = Input.GetAxis("Horizontal");
     
        //回転
        if (horizontal > 0)
            transform.Rotate(0, rotateSpeed * Time.fixedDeltaTime, 0);
        if (horizontal < 0)
            transform.Rotate(0, (-1) * rotateSpeed * Time.fixedDeltaTime, 0);

        //キャラクターの前方方向
        charactorForward = Vector3.Scale(tr.forward, new Vector3(1, 0, 1)).normalized;
        //移動ベクトル
        moveVector = charactorForward * vertical * Time.fixedDeltaTime * speed; 
        //移動
        rb.velocity = new Vector3(moveVector.x, rb.velocity.y, moveVector.z);
    }
5.カメラを追従させる

メインカメラをキャラクターの子オブジェクトにする
※再生してカメラが3人称視点になっていれば成功

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

【UNITY 逆引き備忘録】キャラクターを動かす(Rigitbody.velocity)

はじめに

本記事では、Unityでキャラクターを動かすまでの手順を備忘録として残す。
移動方法はいくつかあるが、今回はRigitbodyのvelocityを利用する。
手順通りに進めると、以下のようなものができあがる。

↑:前進
↓:後退
←:左回転
→:右回転
カメラがキャラクターを追従

Rigitbody.velocityによる移動

Rigitbodyのvelocity(速度)を直接書き換えることで移動する

メリット

物理演算が適用される
一定の速度で移動するので扱いやすい
単調なゲームに向いている

デメリット

質量や重力を考慮していないので挙動が非現実的
リアルな動作を追求したい場合は向かない

手順

0.前準備

Cube等で床を作り、ColliderをAdd Componentする
(Cube等をCreateした場合、Colliderはデフォルトでセットされている)

1.キャラクターを設置

キャラクターを用意する(めんどくさければとりあえずCubeでもOK)
メインカメラで見える位置に設置する

2.RigitBodyを追加

キャラクターにRigitBodyをAdd Compornentする
Use Gravity  ☑
Is Kinematic ☐
※再生してキャラクターが床をすり抜けて落ちれば成功

3.Colliderを追加

キャラクターにColliderをAdd Compornentする
キャラクターと同じくらいの大きさを設定する
Is Trigger  ☐
※再生してキャラクターが床をすり抜けなくなれば成功

4.スクリプトを追加

キャラクターに下記スクリプトをAdd Compornentする
InspectorからspeedとrotateSpeedを設定する
※再生してキャラクターを操作できれば成功

    public float speed;
    public float rotateSpeed;

    private Transform tr;
    private Rigidbody rb;
    private Vector3 charactorForward;
    private Vector3 moveVector;
    private float vertical;
    private float horizontal;

    void Start()
    {
        tr = this.transform;
        rb = GetComponent<Rigidbody>();
    }

    private void FixedUpdate()
    {
     //上下の入力
        vertical = Input.GetAxis("Vertical");
     //左右の入力
        horizontal = Input.GetAxis("Horizontal");
     
        //回転
        if (horizontal > 0)
            transform.Rotate(0, rotateSpeed * Time.fixedDeltaTime, 0);
        if (horizontal < 0)
            transform.Rotate(0, (-1) * rotateSpeed * Time.fixedDeltaTime, 0);

        //移動ベクトル
        moveVector = tr.forward * vertical * Time.fixedDeltaTime * speed; 
        //移動
        rb.velocity = new Vector3(moveVector.x, rb.velocity.y, moveVector.z);
    }
5.カメラを追従させる

メインカメラをキャラクターの子オブジェクトにする
※再生してカメラが3人称視点になっていれば成功

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

【Unity】FungusのLuaで非同期処理を待機する方法

結論

Fungusが提供しているLua関数群の「runwait」を用います。

前提

C#側の関数は「IEnumerator」を戻り値にする必要があります。

応用方法

前回の記事のLuaが終了するまで待機する方法とあわせると、

  • Fungus側で会話1
  • Unity側でイベント
  • Fungus側で会話2
  • Unity側で終了を検知

することができるので、チュートリアルやRPGのイベントなどの表現の幅がぐっと広がります。

例)番号順に処理されます

C#側

// UnityのButtonで呼び出す関数
public async void OnFungusAsync()
{
    Debug.Log($"1 Luaの呼び出し開始");

    await _luaScript.OnExecuteAsync();

    Debug.Log($"6 Luaの呼び出し終了");
}

// Luaで呼び出している関数
public IEnumerator OnMoveCoroutine()
{
    Debug.Log("3 C#側で開始");

    // DoTweenを使い移動処理を行う
    transform.DOMove(new Vector3(1f, 1f, 0f), 5f);

    // 移動が終わるまで待機する
    yield return new WaitForSeconds(5f);

    Debug.Log("4 C#側で終了");
}

Lua側

say("2 Lua開始")

-- C#側の非同期関数を呼び出す
runwait(mainmanager.OnMoveCoroutine())

say("5 Lua終了")

その他

非同期を投げっぱなしにする関数「run」も用意されています。

Fungasが提供している関数は

\Assets\Fungus\Thirdparty\FungusLua\Resources\Lua\fungus.txt

を参照するとわかると思います。

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

UnityでVisual Studioがどうしても開かない場合にVSCodeで代用する方法

UnityでVisual Studioが開かない

Unityでは,Edit -> Preferences -> External Toolsから使用するエディタを指定することができます.通常であれば,Visual Studio 2019などがデフォルトのエディタとして設定されていますが,Unity 2020.1.9f1の環境で自動的に開かなくなりました.

Visual Studio Codeは事前にインストールされていると前提で話を進めます.

パスを指定してVisual Studio 2019で開く方法

Cドライブにインストールされたのであれば,C:\Program Files (x86)\Microsoft Visual Studio\2019\Community\Common7\IDE\devenv.exeにVisual Studio 2019の実行ファイルが存在します.

External ToolsのBrowseで直接パスを指定すればVisual Studio 2019を認識します.

ところが,DドライブにVisual Studio 2019をインストールしてしまった場合,UnityはVisual Studioの実行ファイルを見つけることができません.仮にDドライブのdevenv.exeを指定しても,今度はVisual Studio 2019がUnityを見つけられず,Visual StudioでUnityのプロジェクトを開けなくなります.具体的には,Assemply-CSharp.csprojこのプロジェクトの種類の基になるアプリケーションが見つかりませんでした。というエラーが表示されます.

正しく開けないことによる副作用

この状態だと形式的に開くことはできますが,エディタの補完が効かず,効率がかなり落ちます.メモ帳で作業しているのと同じです.Visual Studioのような専用ツールを使って開発するのは,開発を効率化するためなのでこの状態は放置できません.

VSCodeで開く方法

Visual Studio Code(以下,VSCode)は,Visual Studioとは全く別物の統合開発環境です.ただし,Visual Studio相当のことはプラグインやコマンドの組み合わせで実現できます.

UnityでVSCodeを登録する方法

VSCode自体は,標準のインストール先だと,C:\Users\ユーザ名\AppData\Local\Programs\Microsoft VS Code\Code.exeに格納されています.

UnityのEdit -> Preferences -> External ToolsBrowseを選択すると,外部エディタの実行ファイルの位置を選択できるようになります.上記のパスにVSCode本体があるので,登録してください.

VSCodeでUnityを使えるようにする方法

この段階の状態で,UnityのC#スクリプトを開くと様々なインストールを促されます.数百MB単位のデータのダウンロードが発生するため,通信環境は注意してください.

image.png

図のようにFinish!と表示されたら,Unityに依存するC#関係のパッケージのインストールが完了します.

次に,プラグインのインストールですが,①プラグインボタンを押す,②プラグインを検索する,③選択したプラグインをインストールをする.という順番で操作します.

image.png

一応,VSCodeの現行バージョンv1.50.0でおすすめのプラグインは以下の通りです.

  • C#
  • Debugger for Unity
  • Unity Tools
  • C# XML Documentation Comments

好みによっておすすめ

  • C# FixFormat (コードを自動整形するので見た目が確実にきれいになる)
  • eppz! (C# theme for Unity)(色合いがオシャレになる)

基本的に,このあたりまでできると,UnityとVSCodeで開発しやすくなります.Visual Studioも高機能ですが,VSCodeもプラグインを入れればそこそこ高機能にはなります(さすがに,Intel VTuneほど便利にはなりませんが……).Visual Studio CodeはPython,Ruby,PHP,Javascript等のWeb関係の言語と親和性が高いので,末永くお使いできる統合開発環境です.

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