落下地点をマーキングするなど、なにかしらのインジケータとして地面に沿うようになにかを表示したい、という要望があるかと思います。(というかまんまそれがやりたかったんですがw)
こちらの記事(WORKING WITH UNITY3D ENGINE – DECAL EDITOR)を見つけ、Raycastを使ってやってるのかなーと思いたち、実装してみました。
動きとしてはこんな感じになります。
処理の流れ
今回作成したものの流れは以下です。
- ランタイムで新しく
GameObject
を生成する - 各種、表示に必要なコンポーネントを追加
- スクリプトをアタッチされたオブジェクトの
Mesh
情報をコピー -
Update
内で頂点情報を更新
という流れになります。
コード
コードを順番に見ていきます。
GameObjectを生成する
まずは空のGameObject
を生成します。
最終的にこのオブジェクトの頂点情報を更新していきます。
// Create a projected mesh base.
void CreateProjectedObject() {
m_projectedOject = new GameObject("ProjectedObject");
MeshRenderer newRenderer = m_projectedOject.AddComponent<MeshRenderer>();
MeshFilter newMeshFilter = m_projectedOject.AddComponent<MeshFilter>();
// Copy a mesh to the game object.
MeshFilter meshFilter = GetComponent<MeshFilter>();
Mesh mesh = meshFilter.mesh;
newMeshFilter.mesh = mesh;
}
new GameObject()
で新規にゲームオブジェクトを生成します。これを実行した時点で自動的にSceneのルートに配置されます。
今回は親子関係を作る必要がないため親は設定しません。
次に、画面に表示するのに必要なコンポーネントを追加します。
具体的には MeshRenderer
と MeshFilter
です。
そして追加された MeshFilter
の Mesh
に生成元のオブジェクトのメッシュ情報をコピーします。
(頂点情報やindex、レンダリングタイプなど諸々設定する必要があるため)
以上でセットアップは終わりです。
Updateのタイミングで頂点情報を更新
あとは Update
のタイミングで頂点情報を更新してやれば完成です。
計算の手順としては以下です。
- 元のオブジェクトの頂点を取り出す
- 取り出した頂点をワールド空間座標に変換する
- 各頂点をいったん上空に起き、そこから下向きに
Ray
を飛ばす - ヒットしたらその位置を頂点の高さに設定(ヒットしなかったら
0
にする) - 再計算された頂点情報を、投影用オブジェクトの頂点として再設定
という流れです。
頂点をワールド空間座標に変換する
頂点を変換するには以下のようにします。
// 変換用行列を取り出す
Matrix4x4 matrix = meshFilter.transform.localToWorldMatrix;
// 行列を頂点に適用する
Vector3 vec = matrix.MultiplyPoint(vertex);
Rayを飛ばして位置を算出する
頂点が変換できたら、全頂点に対して以下のようにRayを飛ばし、位置を判定します。
foreach (Vector3 vertex in verticies) {
Vector3 vec = matrix.MultiplyPoint(vertex);
vec.y = 1000;
RaycastHit hit;
if (Physics.Raycast(vec, Vector3.down, out hit)) {
Vector3 newVert = vec;
newVert.y = hit.point.y;
newVerticies[i] = newVert;
}
else {
vec.y = 0;
newVerticies[i] = vec;
}
i++;
}
Updateメソッドコード全文
まとめると、頂点位置計算の処理は以下のようになります。
// Update is called once per frame
void Update () {
MeshFilter meshFilter = GetComponent<MeshFilter>();
Matrix4x4 matrix = meshFilter.transform.localToWorldMatrix;
Mesh mesh = meshFilter.mesh;
Vector3[] verticies = mesh.vertices;
Vector3[] newVerticies = new Vector3[verticies.Length];
int i = 0;
foreach (Vector3 vertex in verticies) {
Vector3 vec = matrix.MultiplyPoint(vertex);
vec.y = 1000;
RaycastHit hit;
if (Physics.Raycast(vec, Vector3.down, out hit)) {
Vector3 newVert = vec;
newVert.y = hit.point.y;
newVerticies[i] = newVert;
}
else {
vec.y = 0;
newVerticies[i] = vec;
}
i++;
}
Mesh projectedMesh = m_projectedOject.GetComponent<MeshFilter>().mesh;
projectedMesh.vertices = newVerticies;
// 法線情報の更新が必要な場合は以下を実行する
// projectedMesh.RecalculateNormals();
}
最終コード
動作するコード全文を載せておきます。
using UnityEngine;
using System.Collections;
public class ProjectedMesh : MonoBehaviour {
// Use for projected mesh.
private GameObject m_projectedOject;
// Use this for initialization
void Start () {
CreateProjectedObject();
GetComponent<MeshRenderer>().enabled = false;
}
// Update is called once per frame
void Update () {
MeshFilter meshFilter = GetComponent<MeshFilter>();
Matrix4x4 matrix = meshFilter.transform.localToWorldMatrix;
Mesh mesh = meshFilter.mesh;
Vector3[] verticies = mesh.vertices;
Vector3[] newVerticies = new Vector3[verticies.Length];
int i = 0;
foreach (Vector3 vertex in verticies) {
Vector3 vec = matrix.MultiplyPoint(vertex);
vec.y = 1000;
RaycastHit hit;
if (Physics.Raycast(vec, Vector3.down, out hit)) {
Vector3 newVert = vec;
newVert.y = hit.point.y;
newVerticies[i] = newVert;
}
else {
vec.y = 0;
newVerticies[i] = vec;
}
i++;
}
Mesh projectedMesh = m_projectedOject.GetComponent<MeshFilter>().mesh;
projectedMesh.vertices = newVerticies;
// projectedMesh.RecalculateNormals();
}
// Create a projected mesh base.
void CreateProjectedObject() {
m_projectedOject = new GameObject("ProjectedObject");
MeshRenderer newRenderer = m_projectedOject.AddComponent<MeshRenderer>();
MeshFilter newMeshFilter = m_projectedOject.AddComponent<MeshFilter>();
// Copy a mesh to the game object.
MeshFilter meshFilter = GetComponent<MeshFilter>();
Mesh mesh = meshFilter.mesh;
newMeshFilter.mesh = mesh;
}
}