はじめに
今回は比較的単発なテクニックを試したのでそれを公開したい。
UnityとGodotの違い
Tipsを紹介する前に、今回のTipsに関わるUnityとGodotでの決定的な違いについて、簡単に触れておきたい。
3Dモデル(オブジェクト)の表示・非表示に関するレイヤーの場所が違う
UnityでVRMを読み込むと次のようなヒエラルキー構造になる。
UnityではGameObjectがレイヤーを持っており、どの位置でもレイヤーを切り替えて好きな目的で使用することができる。
一方、GodotでVRMを読み込むと次のようになる。
たとえばVRM自身のノードには、プロパティのどこにもレイヤーはない。そもそもUnityとGodotでは基本的な構造が違うから仕方ない。
単に表示・非表示するだけならshow()とかそういう感じのメソッドを使えばいいだけだが、レイヤー分けして管理したり、優先度をつけて表示するときには困りそうだ。
GodotではMeshInstance3Dのレイヤーを使う
MeshInstance3DはVisualInstance3Dを継承しており、そのクラスには layers
プロパティがある。
この layers
プロパティはプロジェクトの設定からもレイヤー名を変えられるようになっている。どうやらこれがUnityのGameObjectのlayerに相当するものと考えてよさそうだ。
CSGPrimitive3Dを継承する基本的な図形も、当然このVisualInstance3Dを継承しているので layers
は存在するのがわかる。
GodotのノードというのはUnityのGameObject(とComponent)と違って、一つのノードに複数の機能をもたせる考え方ではないようだ。
つまり、機能単位でノード・子ノードの構成を作る必要がある。
- 3Dモデルの表示に関わるノード
- 3Dモデルのアニメーションの再生に関わるノード
- 3Dモデルの衝突に関するノード
逆に言うと、特定の操作をするなら取得すべきノードが自ずと決まってくるので、わかりやすいかもしれない。
Tips
さて、違いを踏まえた上で、今回の記事のメインに進む。
3Dモデルのメッシュのレイヤーを一度に変える
前述の通り、Unityならば親のGameObjectのレイヤーを切り替えるだけで済んだが、Godotではそうも行かない。
VisualInstance3Dを継承するクラスを型に持つノード全部の layers
を切り替える
func set_layer_value(layer:int):
return 1 << (layer - 1)
func recur_set_layer(layer: int, targets: Node):
var children = targets.get_children(true)
for child in children:
if child is MeshInstance3D:
(child as MeshInstance3D).layers = set_layer_value(3) #設定したいレイヤー番号
recur_set_layer(layer, child)
再帰処理でMeshInstance3Dを型に持つノードに対して layers
を設定してあげればいい。
これで例えばVRMを特定のレイヤーに表示して、別のレイヤーの3Dモデルと重ねて表示などができるようになる。
ただメッシュの部位によってレイヤーを変えたいとかそういう追加機能をつけたい場合、渡す引数を工夫する必要がある。
3Dモデルの全体の大きさを取得する
ファイルサイズではなく、3Dモデルを構成するメッシュの端から端までのつまりBoundingのサイズのことである。
このテクニックはなにもGodotだけではなく、Unityでもよくあるものだろう。
公式マニュアルでは次のページだ。
MeshInstance3D
そしてGodotではBoundingなクラスがAABBだ。
AABB
ぶっちゃけ、Claude先生に教わって作ったので、AABBとかギャグかなと思う名前のクラスがあることにも驚いた。
func get_total_aabb(targets) -> AABB:
var total_aabb = AABB()
var first = true
# 全てのMeshInstance3Dを再帰的に検索
var allmeshes = get_all_mesh_instances(targets)
for child: MeshInstance3D in allmeshes:
var local_transform = child.get_global_transform()
var mesh_aabb = child.get_aabb()
# メッシュのAABBをグローバル座標系に変換
var global_aabb = local_transform * mesh_aabb
if first:
total_aabb = global_aabb
first = false
else:
# AABBを結合
total_aabb = total_aabb.merge(global_aabb)
return total_aabb
func get_all_mesh_instances(node: Node) -> Array:
"""
子・孫階層すべてのMeshInstance3Dをひとまとめに取得する
"""
var mesh_instances = []
if node is MeshInstance3D:
mesh_instances.append(node)
for child in node.get_children():
mesh_instances.append_array(get_all_mesh_instances(child))
return mesh_instances
func any_function():
...
get_total_aabb(vrm)
...
get_total_aabb
がメインのメソッドである。
その中では受け渡された最初のノードを起点とし、 get_all_mesh_instances
メソッドで再帰処理にてMeshInstance3Dを探し出し、配列にしている。
そしてMeshInstance3Dの配列に対して get_aabb()
メソッドでAABB型のデータを取得し、Transformを使ってグローバル座標のAABBに変換し、それをひたすら merge()
メソッドで結合していく。
printで出力して確認
AABBにはpositionとsizeがある。Sがおそらくsizeだろう。
出力された値と、Unityで作った現行のVRMViewMeisterを使い、身長として表示した値を比べてみた。
positionを考慮する必要があるのかもしれない。
1.77005 - 0.13554 = 1.634496
sizeをpositionで減算したところ大体同じ値となった。Unityで算出した値とGodotの今回のテクニックで取得した値、どうやら問題なさそうだ。
(若干の誤差は気にしないでおく)
おわりに
自分にとって必要なテクニックを発見したので、覚え書きとして今回の記事にまとめた次第である。
この記事が皆様のお役に立てれば幸いだ。