LoginSignup
0
0

More than 3 years have passed since last update.

HTML Agility Packを使ってUnityで使える3Dマークアップ言語を実装してみる

Posted at

目的

3Dオブジェクトを記述する3Dマークアップ言語をUnityで実装してみる。

マークアップ言語?

ざっくりHTMLみたいな、A-Frameに近いものを作ってみます。

道具立て

HTMLライクなマークアップ言語のパースに、HTML Agility Packを使います。
NuGetで入れることもできますし、サイトからnupkgをダウンロードして7zipなどで展開して、DLLだけ使うということもできます。
.Net 4.5用だったり、.Net Standard 2.0用だったり、いくつか種類があるので、自分のプロジェクトに合ったものを使います。
UnityのDLLのImportセッティングのところで、適切なプラットフォームに対して、適切なDLLが使われるように設定しましょう。

実際のプログラム

以下のプログラムがパーサーのエントリー部分になります。

HomlParser.cs
using System.Collections;
using System.Collections.Generic;
using System.Runtime;
using UnityEngine;
using HtmlAgilityPack;
using Homl.DOM;
using System.Reflection;

namespace Homl.Parser
{
    public class HomlParser
    {
        public static Dictionary<string, ParseNode> nodeTemplates;

        public HomlParser()
        {
            if(nodeTemplates == null)
            {
                nodeTemplates = new Dictionary<string, ParseNode>();

                nodeTemplates.Add("a-scene", new Body());
                nodeTemplates.Add("a-box", new Cube());
                nodeTemplates.Add("a-cylinder", new Cylinder());
                nodeTemplates.Add("a-sphere", new Sphere());
                nodeTemplates.Add("a-link", new ATag());
                nodeTemplates.Add("a-text", new Text());
                nodeTemplates.Add("homl", new Homl());
            }
        }

        public Document Parse(string homl)
        {
            var htmlDoc = new HtmlAgilityPack.HtmlDocument();
            htmlDoc.LoadHtml(homl);

            GameObject documentGO = new GameObject("Document");
            Document document = documentGO.AddComponent<Document>();
            BoxCollider bc = documentGO.AddComponent<BoxCollider>();

            walkinChild(htmlDoc.DocumentNode.ChildNodes, documentGO);

            bc.size = new Vector3(0.3f, 0.3f, 0.3f);
            return document;
        }

        private void walkinChild(HtmlAgilityPack.HtmlNodeCollection nodes, GameObject parent)
        {
            foreach (HtmlAgilityPack.HtmlNode node in nodes)
            {
                GameObject nodeObject = null;
                Debug.Log(node.Name);

                if (nodeTemplates.ContainsKey(node.Name))
                {
                    nodeObject = nodeTemplates[node.Name].parse(node, parent);
                } else
                {
                    nodeObject = parent;
                }

                if (node.HasChildNodes)
                {
                    walkinChild(node.ChildNodes, nodeObject);
                }

            }
        }
    }
}

再帰的にマークアップの構造をたどり、タグ名の辞書から該当タグをパースするオブジェクトを探しながらUnityのGameObjectを作成していきます。

各タグのパーサーは、例えばCubeに対するパーサーが以下のようになっています。

Cube.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using HtmlAgilityPack;

namespace Homl.Parser
{
    public class Cube : ParseNode
    {
        public override GameObject parse(HtmlNode homlNode, GameObject parent)
        {
            PrimitiveProducer pp = GameObject.Find("PrimitiveProducer").GetComponent<PrimitiveProducer>();
            GameObject cube = GameObject.Instantiate(pp.getCube());
            float x = float.Parse(homlNode.GetAttributeValue("x", "0.0f"));
            float y = float.Parse(homlNode.GetAttributeValue("y", "0.0f"));
            float z = float.Parse(homlNode.GetAttributeValue("z", "0.0f"));
            float s = float.Parse(homlNode.GetAttributeValue("size", "0.1f"));
            string color = homlNode.GetAttributeValue("color", "white");

            cube.transform.parent = parent.transform;
            cube.transform.Translate(new Vector3(x, y, z));
            cube.transform.localScale = new Vector3(1.0f, 1.0f, 1.0f);

            Material mat = cube.GetComponent<Renderer>().material;
            mat.color = ColorManager.Instance.getColor(color);
            mat.SetColor("_BaseColor", ColorManager.Instance.getColor(color));

            Vector3 scale = new Vector3(s, s, s);
            Vector3[] verts = cube.GetComponent<MeshFilter>().mesh.vertices;
            Vector3[] newverts = new Vector3[verts.Length];
            int i = 0;
            foreach (Vector3 v in verts)
            {
                v.Scale(scale);
                newverts[i] = v;
                i++;
            }
            cube.GetComponent<MeshFilter>().mesh.vertices = newverts;
            BoxCollider bc = cube.GetComponent<BoxCollider>();
            bc.size = scale;

            DOM.Cube cb = cube.AddComponent<DOM.Cube>();
            return cube;
        }
    }
}

PrimitiveProducerは、プリミティブの3DオブジェクトPrefabの参照を管理しているオブジェクトです。
Attributeの指定の仕方などは、本家のA-Frameと異なるところなので、コンポーネント志向の構造と合わせて抜本的な見直しは必要ですが、
これに加えて、DOMのクラス群を合わせて動かすことで、3DオブジェクトをマークアップからUnity内に生成して、リンクなどの仕組みを作ることもできるようになりました。

表示結果

20191223_homl.PNG

もとになったマークアップ

天気予報の雪だるまのつもり

<homl>
<head>
<title>Weather</title>
</head>
<body>
<a-scene wx=0.2 wy=0.001 wz=0.2>
<a-cylinder height=0.02 r=0.02 x=0 y=0.18 z=0 color=red />
<a-sphere r=0.1 x=0 y=0.05 z=0 color=white />
<a-sphere r=0.08 x=0 y=0.14 z=0 color=white />
</a-scene>
</body>
</homl>

機能テスト用

<homl>
<head>
<title>TestHoloML</title>
</head>
<body>
<a-scene wx=0.2 wy=0.2 wz=0.2>
<a-box size=0.1 x=0 y=0 z=0 color=blue>
 <a-cylinder height=0.05 r=0.02 x=0.1 y=0.1 z=0.1 color=red />
 <a-sphere r=0.05 x=-0.1 y=-0.1 z=-0.1 color=green />
 <a-text size=0.1 x=0 y=0 z=0 color=blue>ABCDEF</text>
</a-box>
<a-link href=”test.homl”>
 <a-box size=0.1 x=0.1 y=-0.1 z=0.1 color=white />
</a-link>
</a-scene>
</body>
</homl>

3Dグラフ

<homl>
<head>
<title>3D Graph</title>
</head>
<body>
<a-scene wx=0.5 wy=0.001 wz=0.2>
<a-cylinder height=0.2 r=0.05 x=-0.2 y=0.2 z=0 color=red />
<a-cylinder height=0.12 r=0.05 x=-0.1 y=0.12 z=0 color=red />
<a-cylinder height=0.14 r=0.05 x=0 y=0.14 z=0 color=red />
<a-cylinder height=0.08 r=0.05 x=0.1 y=0.08 z=0 color=red />
<a-cylinder height=0.05 r=0.05 x=0.2 y=0.05 z=0 color=red />
</a-scene>
</body>
</homl>

現状ではプリミティブオブジェクトの組み合わせだけなのですが、このように表示が可能になりました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0