MetaQuest3でMRデモ 3. QuestCameraTools-Unity/QRTracking-6000を自作モデルで実装でQRコードで表示させる3Dモデルを時間経過で切り替える.
初めはMetaQuest3でVRデモ 4. オブジェクトを初期位置にリスポーンするUI作成のようにUIのボタンで制御しようとしましたが,RayでもPokeでも反応せず,調べてみるとQR Tracker等と競合を起こしそうとのことなので,コントローラーのボタンで制御しました.
また,ボタンのOnClick処理では関数の呼び出しが難しかったので,結果としてコントローラーのボタンで制御のほうがシンプルにまとまりました.
ここでの処理を応用すれば,XRに限らず他のUnityでの実装例でも同じことができるはずです.
3Dモデルの準備
Assets > Resources > 新規フォルダでフォルダ内に3Dモデル(fbxとobjは動作確認済)を入れる.
おそらく名前順に読み込んでリストに格納するので,表示する順番に応じた連番などの名称にすればいいと思う.
Prefabの編集
MetaQuest3でMRデモ 3. QuestCameraTools-Unity/QRTracking-6000を自作モデルで実装で作成したPrefabを編集する.
changemodel.csを作成する.
このコード(全文は本節の最後の記載)では,以下の処理を実行している.
void Start()関数
Resourcesフォルダ内に保存した表示切替用の3Dモデルを読み込んで,メッシュとマテリアルをリストに格納する.
この関数はQRコードが読み込まれたときに起動するので,3Dモデルが多かったり頂点数や面数が多かったりすると,読み込みのため少し待つ可能性がある.
また,最初に表示した3Dモデルにリセットする機能を追加するので,最初の3Dモデルのメッシュとマテリアルを別の関数で保存している.
3Dモデルによってはオブジェクトでなくその子オブジェクトに当たる「default」にMesh Lenderやマテリアルが定義されているケースがあるので,その際はmodelPrefab.GetComponent<MeshFilter>();をmodelPrefab.GetComponentInChildren<MeshFilter>();と書き換える.
void Start()
{
// 自身のGameObjectをターゲットに設定
targetObject = this.gameObject;
// Resourcesからモデルを読み込む
Object[] loadedObjects = Resources.LoadAll(resourcesFolderPath, typeof(GameObject));
foreach (Object obj in loadedObjects)
{
GameObject modelPrefab = obj as GameObject;
if (modelPrefab == null) continue;
// モデル内のメッシュとマテリアルを取得
MeshFilter mf = modelPrefab.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = modelPrefab.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = modelPrefab.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = modelPrefab.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
meshes.Add(mf.sharedMesh);
materials.Add(mr.sharedMaterials);
}
}
// 最初のMeshとMaterialを保存
Transform childTransform = targetObject.transform.Find(childObjectName);
if (childTransform != null)
{
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = modelPrefab.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = modelPrefab.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
originalMesh = mf.sharedMesh;
originalMaterials = mr.sharedMaterials;
}
}
}
void Update()関数
QRコードが読み込まれた後,毎フレーム実行される関数.判定が複雑なので,必ずこの順番で記載すること.
こちらでも「default」にMesh Lenderやマテリアルが定義されている場合は,記載を書き換える.
右コントローラーのAボタン(Button.One)が押されると,isCycling = trueになり,frameCounter++以降の処理が実行される.もしframeCounterがframeDelayより大きくなったら,Mesh Lenderとマテリアルがリスト内の(currentMeshIndex+1)番目のものに更新される.currentMeshIndexが1増加し,もし全部回りきった際は0に戻ってくる.
右コントローラーのBボタン(Button.Two)が押されると,Mesh Lenderとマテリアルが初期状態に戻り,frameCounterもcurrentMeshIndexも0になる.isCycling = falseになっているため,frameCounterが増加しないので,初期状態のまま静止した状態になる.
void Update()
{
if (OVRInput.GetDown(OVRInput.Button.One))
{
isCycling = true;
}
if (OVRInput.GetDown(OVRInput.Button.Two))
{
isCycling = false;
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = childTransform.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = childTransform.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
mf.mesh = originalMesh;
mr.materials = originalMaterials;
}
currentMeshIndex = 0;
frameCounter = 0;
}
if (!isCycling || meshes.Count == 0)
return;
frameCounter++;
if (frameCounter >= frameDelay)
{
frameCounter = 0;
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = childTransform.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = childTransform.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
mf.mesh = meshes[currentMeshIndex];
mr.materials = materials[currentMeshIndex];
currentMeshIndex = (currentMeshIndex + 1) % meshes.Count;
}
}
}
changemodel.cs(全文)
using UnityEngine;
using System.Collections.Generic;
using TMPro;
public class ChangeModel : MonoBehaviour
{
[Header("子オブジェクト名(メッシュを差し替える)")]
public string childObjectName = "MeshHolder";
[Header("Resources内のモデル読み込みフォルダ名")]
public string resourcesFolderPath = "Models"; // 例: Assets/Resources/Models にFBXを置く
[Header("切り替えフレーム間隔")]
public int frameDelay = 10;
private List<Mesh> meshes = new List<Mesh>();
private List<Material[]> materials = new List<Material[]>();
private int currentMeshIndex = 0;
private int frameCounter = 0;
private GameObject targetObject;
private Mesh originalMesh;
private Material[] originalMaterials;
private bool isCycling = false;
private Transform childTransform;
void Start()
{
// 自身のGameObjectをターゲットに設定
targetObject = this.gameObject;
// Resourcesからモデルを読み込む
Object[] loadedObjects = Resources.LoadAll(resourcesFolderPath, typeof(GameObject));
foreach (Object obj in loadedObjects)
{
GameObject modelPrefab = obj as GameObject;
if (modelPrefab == null) continue;
// モデル内のメッシュとマテリアルを取得
MeshFilter mf = modelPrefab.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = modelPrefab.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = modelPrefab.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = modelPrefab.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
meshes.Add(mf.sharedMesh);
materials.Add(mr.sharedMaterials);
}
}
// 最初のMeshとMaterialを保存
childTransform = targetObject.transform.Find(childObjectName);
if (childTransform != null)
{
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = modelPrefab.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = modelPrefab.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
originalMesh = mf.sharedMesh;
originalMaterials = mr.sharedMaterials;
}
}
}
void Update()
{
if (OVRInput.GetDown(OVRInput.Button.One))
{
isCycling = true;
}
if (OVRInput.GetDown(OVRInput.Button.Two))
{
isCycling = false;
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = childTransform.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = childTransform.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
mf.mesh = originalMesh;
mr.materials = originalMaterials;
}
currentMeshIndex = 0;
frameCounter = 0;
}
if (!isCycling || meshes.Count == 0)
return;
frameCounter++;
if (frameCounter >= frameDelay)
{
frameCounter = 0;
MeshFilter mf = childTransform.GetComponent<MeshFilter>();
//default内で定義している際は MeshFilter mf = childTransform.GetComponentInChildren<MeshFilter>();
MeshRenderer mr = childTransform.GetComponent<MeshRenderer>();
//default内で定義している際は MeshRenderer mr = childTransform.GetComponentInChildren<MeshRenderer>();
if (mf != null && mr != null)
{
mf.mesh = meshes[currentMeshIndex];
mr.materials = materials[currentMeshIndex];
currentMeshIndex = (currentMeshIndex + 1) % meshes.Count;
}
}
}
}
QRObject > ModelContainerのコンポーネントとしてchangemodel.csを追加する.
Resource内に保存したモデルのフォルダ名,フレーム遅延(1秒=60フレーム),子オブジェクトの名前をそれぞれ記入する.
