🎄teratailクリスマスプレゼントキャンペーン2024🎄』開催中!

\teratail特別グッズやAmazonギフトカード最大2,000円分が当たる!/

詳細はこちら
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

Q&A

解決済

1回答

2622閲覧

影のできる位置を求めてコライダーをつけたい

a161036

総合スコア7

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

Unity3D

Unity3Dは、ゲームや対話式の3Dアプリケーション、トレーニングシュミレーション、そして医学的・建築学的な技術を可視化する、商業用の開発プラットフォームです。

Unity

Unityは、Unity Technologiesが開発・販売している、IDEを内蔵するゲームエンジンです。主にC#を用いたプログラミングでコンテンツの開発が可能です。

0グッド

1クリップ

投稿2020/01/16 09:12

3dゲーム上で影の上を歩けるようにしたい

大学の卒業制作でunityを使ってVRゲームを作っています。

オブジェクトに光をあてて、壁に投影させた影の上をキャラクターに歩かせたいです。
そのために、影のできる位置を座標でだしてその位置にコライダーオブジェクトを配置したいと考えています。

どのようなコードだと可能か、教えていただけると嬉しいです。
自分がまったくの初心者なので、少しでもアドバイス等あれば教えていただきたいです。

発生している問題・エラーメッセージ

エラーメッセージ

該当のソースコード

ソースコード

試したこと

ここに問題に対して試したことを記載してください。

補足情報(FW/ツールのバージョンなど)

ゲームの詳しい説明を追加します。

壁の前に様々な形のオブジェクトがあり、プレイヤーはそのオブジェクトを組み合わせて
ライトを当てながら、壁に影を投影させていきます。

その影を道にしながら、キャラクターがスタートからゴール(ここでは壁の端から端)を渡り切れば
クリアというルールです。

このようなことが実行できれば、座標を求めて~のやり方でなくても教えていただきたいです。
よろしくお願いします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

a161036

2020/01/20 08:32

ご指摘ありがとうございます。 質問内容について、至らず申し訳ありませんでした。 現在同時進行で、raycastの方法も試行しております。 raycastを使う方法以外も調べた方がいいとアドバイスがあったため、ほぼ同じ質問内容になってしまいました。 申し訳ありませんでした。
guest

回答1

0

ベストアンサー

シャドウボリューム法をアレンジして利用できないでしょうかね?
元のメッシュを引き伸ばしてシャドウボリュームメッシュを作り、これを影描画に使う代わりにメッシュコライダーにセットしてやれば、影の形で衝突判定ができないでしょうか。

まずこのようなメッシュ加工用スクリプトを用意しておいて...

C#

1using System; 2using System.Collections.Generic; 3using System.Linq; 4using UnityEngine; 5 6public static class ShadowMeshAssembler 7{ 8 public static Mesh AssembleShadowMesh(Mesh sourceMesh) 9 { 10 if (sourceMesh == null) 11 { 12 throw new ArgumentNullException(nameof(sourceMesh)); 13 } 14 15 if (sourceMesh.GetTopology(0) != MeshTopology.Triangles) 16 { 17 throw new InvalidOperationException("Topology is not Triangles."); 18 } 19 20 var sourceVertices = sourceMesh.vertices; 21 var sourceIndices = sourceMesh.triangles; 22 var sourceTriangleCount = sourceIndices.Length / 3; 23 24 // 元のメッシュは頂点を共有して三角形を作っている箇所があるかもしれないが、面倒を減らすため 25 // 三角形単位にバラバラにしてしまい、法線も元の法線の代わりに三角形の面法線を使うことにする 26 // (なお、法線は後述のShadowVolumeCaster中で面が光源の方を向いているかどうかを判定するのに使う) 27 var vertices = sourceIndices.Select(i => sourceVertices[i]).ToArray(); 28 var normals = Enumerable.Range(0, sourceTriangleCount).SelectMany( 29 triangleIndex => 30 { 31 var i0 = triangleIndex * 3; 32 var v0 = vertices[i0]; 33 var v1 = vertices[i0 + 1]; 34 var v2 = vertices[i0 + 2]; 35 var faceNormal = Vector3.Cross(v1 - v0, v2 - v0).normalized; 36 return Enumerable.Repeat(faceNormal, 3); 37 }).ToArray(); 38 var indices = Enumerable.Range(0, sourceIndices.Length).ToArray(); 39 40 // 位置が同じ頂点を識別できるように目印を付けて... 41 var vertexReferences = vertices.Select((_, i) => new VertexReference {Index = i}).ToArray(); 42 foreach (var g in vertexReferences.GroupBy(v => vertices[v.Index])) 43 { 44 var refs = g.ToArray(); 45 var identifier = new VertexIdentifier(); 46 foreach (var v in refs) 47 { 48 v.Identifier = identifier; 49 } 50 } 51 52 // 各頂点を結ぶエッジを求め... 53 var edges = Enumerable.Range(0, sourceTriangleCount).SelectMany( 54 triangleIndex => 55 { 56 var i0 = triangleIndex * 3; 57 return Enumerable.Range(0, 3).Select(j => new Edge {From = vertexReferences[i0 + j], To = vertexReferences[i0 + ((j + 1) % 3)]}); 58 }).ToArray(); 59 60 // あるエッジに対して、両端の2つの座標を逆向きに結ぶエッジを探し 61 // ババ抜きの要領でペアを作って取り除いていく 62 // すべての三角形が面積を持ち(縮退した三角形を持つものはダメ)、 63 // メッシュ表面に破れがなく(穴が開いていたり、Quadのように外周を 64 // 持っているものはダメ)、その他トポロジー的に特殊な構造がない 65 // (面の向きが部分的に反転していたり、1つのエッジから3つ以上の面が 66 // 生えているようなものはダメ)メッシュなら、それぞれのエッジに対して 67 // 対応するパートナーが1本だけ見つかるはず 68 var tempEdgeList = edges.ToList(); 69 var edgePairs = new List<EdgePair>(); 70 try 71 { 72 while (tempEdgeList.Any()) 73 { 74 var first = tempEdgeList[0]; 75 tempEdgeList.RemoveAt(0); 76 var second = tempEdgeList.Single(e => (e.To.Identifier == first.From.Identifier) && (e.From.Identifier == first.To.Identifier)); 77 tempEdgeList.Remove(second); 78 edgePairs.Add(new EdgePair {First = first, Second = second}); 79 } 80 } 81 catch (InvalidOperationException e) 82 { 83 throw new InvalidOperationException("Edge pairing failed. Is the source mesh sure a manifold?", e); 84 } 85 86 // 見つかったエッジペアの間に面を張る 87 var additionalIndices = new List<int>(); 88 foreach (var edgePair in edgePairs) 89 { 90 var i0 = edgePair.First.From.Index; 91 var i1 = edgePair.First.To.Index; 92 var i2 = edgePair.Second.From.Index; 93 var i3 = edgePair.Second.To.Index; 94 95 if (normals[i0] == normals[i3]) 96 { 97 // このエッジペアを挟んだ2枚の面が真っ平らになっているなら 98 // 引き伸ばし過程でこのペアが引き離されることはないだろうから 99 // (つまり、一方の三角形が光源に対して表を向いているなら 100 // それと同じ平面上にあるもう一方の三角形も表を向いている 101 // はずだから)面を張る必要はない 102 continue; 103 } 104 105 additionalIndices.Add(i0); 106 additionalIndices.Add(i3); 107 additionalIndices.Add(i1); 108 additionalIndices.Add(i2); 109 additionalIndices.Add(i1); 110 additionalIndices.Add(i3); 111 } 112 113 var mesh = new Mesh 114 { 115 name = sourceMesh.name + " (Shadow)", 116 vertices = vertices, 117 normals = normals, 118 triangles = indices.Concat(additionalIndices).ToArray() 119 }; 120 return mesh; 121 } 122 123 private class VertexReference 124 { 125 public int Index; 126 public VertexIdentifier Identifier; 127 } 128 129 private class VertexIdentifier 130 { 131 } 132 133 private struct Edge 134 { 135 public VertexReference From; 136 public VertexReference To; 137 } 138 139 private struct EdgePair 140 { 141 public Edge First; 142 public Edge Second; 143 } 144}

これでたとえばキューブを加工すると、下図のようにエッジ間に面が作られます。エッジの間に面を張っておくことによって、頂点を動かしてエッジ同士が離れても隙間を生じさせずに形状を変形させることができるかと思います。

図1

実際にオブジェクトにアタッチするスクリプトは下記のようにしてみました。

C#

1using System.Linq; 2using UnityEngine; 3 4[RequireComponent(typeof(MeshFilter))] 5public class ShadowVolumeCaster : MonoBehaviour 6{ 7 // ここに光源となるオブジェクトをセットしておく 8 // 任意のタイミングで付け外ししてよく、もしセットされていなければ 9 // シャドウボリュームは非アクティブになる 10 public Transform lightTransform; 11 12 // シャドウボリュームを何m引き延ばすか? 13 public float extension = 10.0f; 14 15 // 余計な衝突判定を防止したい場合、ここにシャドウボリューム用のレイヤーを指定する 16 [SerializeField] private int shadowLayer; 17 18 // シャドウボリュームを実際にレンダリングして可視化してみたい場合、ここにマテリアルをセットする 19 [SerializeField] private Material shadowMaterial; 20 21 private GameObject shadowVolume; 22 private Mesh shadowMesh; 23 private MeshCollider shadowCollider; 24 private Vector3[] shadowLocalPositions; 25 private Vector3[] shadowWorldPositions; 26 private Vector3[] shadowLocalNormals; 27 private Vector3[] shadowWorldNormals; 28 29 private void Reset() 30 { 31 this.shadowLayer = LayerMask.NameToLayer("Default"); 32 } 33 34 private void Start() 35 { 36 // Start時にこのオブジェクトのメッシュを基にシャドウボリュームを作成し、 37 // それをセットしたMeshColliderを持つオブジェクトを作成する 38 this.shadowMesh = ShadowMeshAssembler.AssembleShadowMesh(this.GetComponent<MeshFilter>().sharedMesh); 39 this.shadowLocalPositions = this.shadowMesh.vertices; 40 this.shadowWorldPositions = new Vector3[this.shadowLocalPositions.Length]; 41 this.shadowLocalNormals = this.shadowMesh.normals.Where((_, i) => (i % 3) == 0).ToArray(); 42 this.shadowWorldNormals = new Vector3[this.shadowLocalNormals.Length]; 43 this.shadowVolume = new GameObject(this.name + " (Shadow)") {layer = this.shadowLayer}; 44 this.shadowCollider = this.shadowVolume.AddComponent<MeshCollider>(); 45 this.shadowCollider.sharedMesh = this.shadowMesh; 46 if (this.shadowMaterial == null) 47 { 48 return; 49 } 50 51 this.shadowVolume.AddComponent<MeshFilter>().sharedMesh = this.shadowMesh; 52 this.shadowVolume.AddComponent<MeshRenderer>().sharedMaterial = this.shadowMaterial; 53 } 54 55 // Updateだと、このスクリプトがメッシュを更新した後で他のスクリプトによって 56 // オブジェクトの位置その他が書き換えられた場合にずれが生じてしまうため、 57 // LateUpdateを使うようにしてそうなる可能性を低減した 58 private void LateUpdate() 59 { 60 if (this.lightTransform == null) 61 { 62 this.shadowVolume.SetActive(false); 63 return; 64 } 65 66 var localToWorldPosition = this.transform.localToWorldMatrix; 67 var localToWorldNormal = localToWorldPosition.inverse.transpose; 68 var lightPosition = this.lightTransform.position; 69 70 // ワールド法線を求める 71 // 向きだけ分かれば十分なので、長さの正規化は省略 72 for (var i = 0; i < this.shadowLocalNormals.Length; i++) 73 { 74 this.shadowWorldNormals[i] = localToWorldNormal.MultiplyVector(this.shadowLocalNormals[i]); 75 } 76 77 // ワールド座標を求める 78 // このとき、光源と逆方向に向いている頂点は光源から遠ざけるように移動させておく 79 for (var i = 0; i < this.shadowLocalPositions.Length; i++) 80 { 81 var p = localToWorldPosition.MultiplyPoint3x4(this.shadowLocalPositions[i]); 82 var d = p - lightPosition; 83 var n = this.shadowWorldNormals[i / 3]; 84 if (Vector3.Dot(n, d) > 0.0f) 85 { 86 p += d.normalized * this.extension; 87 } 88 89 this.shadowWorldPositions[i] = p; 90 } 91 92 // メッシュを更新 93 this.shadowMesh.vertices = this.shadowWorldPositions; 94 this.shadowMesh.RecalculateBounds(); 95 this.shadowCollider.sharedMesh = this.shadowMesh; 96 this.shadowVolume.SetActive(true); 97 } 98 99 private void OnDestroy() 100 { 101 Destroy(this.shadowVolume); 102 Destroy(this.shadowMesh); 103 } 104}

Cube、Sphere、Capsule、Cylinderにスクリプトをアタッチしてみたところ、下図のように影領域を表すメッシュが作られました。

図2

ただし、この方法ですとコライダー自体の形状が瞬間瞬間で変化していくという挙動になりますので、もしたとえば影の上にキャラクターが乗っている状態でライトを素早く動かして影の位置を変化させキャラクターを吹っ飛ばそうと思っても、うまく力が伝達されず意図通りにならない可能性もありそうです。それに対して、「影に衝突判定を付けることは可能でしょうか」でsakura_hanaさんからのご提案のあった「Raycastで壁の位置算出→Colliderオブジェクト配置→距離から大きさ調整」であれば物理的アクションへの追従性に優れているように思います。

卒業研究ということですと、まさか提出期限は今年度中でしょうかね?
締め切りが迫っているのでしたら、むやみに未知の手法に手を出すよりもご質問者さんが挙動を十分ご存じの方法をとることにして、ゲームデザインの方をそれに合わせていくらか妥協した方がいいかもしれません(それだと新規性に欠けるようでしたら、未完成で提出することになるリスクを冒してでもやってみるというのはアリかもしれませんが...)。
期限が来年度以降なら時間は十分あるかと思いますので、さまざまな方法で試行錯誤してみますとプログラミング習熟の観点からも得るものが大きいんじゃないかと思います。

投稿2020/01/17 11:03

編集2020/01/20 22:01
Bongo

総合スコア10811

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

a161036

2020/01/20 08:30

ご回答ありがとうございます。 参考にさせていただきます。 メッシュ加工用スクリプトの方で var edgePairs = new List<(Edge, Edge)>(); の部分を含む8つの部分で 「System ValueTpule2 は定義 またはインストールされていません」 というエラーがでました。 調べてみたところ、NET Framework4.7 NETStandard 1.7 のバージョンでできるとあったので、バージョンを変更したのですが、エラーが消えません。 Bongoさんの作業環境では動作しているようなので、使えるようにしたいのですがどうしたら良いでしょうか。
Bongo

2020/01/20 22:02

Unityをアップグレードしてもダメでしたか?ShadowMeshAssemblerのコードをタプルを使わないように変更してみましたが、これならどうでしょうか。
a161036

2020/01/21 03:02

本当にありがとうございます。 エラーが消えました! なんとお礼をいったらいいかわかりません。 また質問させてください。本当にありがとうございます。
a161036

2020/01/21 03:55

重ねて申し訳ありません。 そのまま、コピペでアタッチしてみたんですが、Can't add script behaviour VisualContainerAsset",Can't Add Script "The script needs to derive from MonoBehaviour!" とでるのは、 スクリプトの中の名前と、プロジェクトの名前と違っているからという認識であっていますか? 自分でもしらべてはみたんですが確信がもてなくて、質問に至りました。
Bongo

2020/01/21 12:52

そうですね、もしスクリプトファイルの名前とスクリプトファイル中で定義したクラス名が一致しなければアタッチできないと思います。もしいずれかを変更した心当たりがあればその可能性もあるでしょう。 ですが、「VisualContainerAsset」なんて名前を付けた覚えはないかと思います。以前「スクリプトがオブジェクトにアタッチ出来ない」(https://teratail.com/questions/128536 )とのご質問を見かけたのですが、Unityのバージョンによってはメッセージにこの謎のクラスが登場してしまうようですね。ともかくどこか書き換えた心当たりがあるようなら、そのスクリプトを調べてみるのがいいでしょう。 ※なお、念のため申し上げますと「ShadowMeshAssembler」の方はオブジェクトにアタッチして使うためのものではなく、メッシュ組み立て機能を単独のファイルに分けて整理しただけのものです。ですのでこちらのファイルはプロジェクト内に置いておくだけでいいでしょう。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.36%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問