前提・実現したいこと
ここに質問の内容を詳しく書いてください。
Unity 3Dでゲームを作っています。簡単に言うと彫刻ゲームのようなものをつくりたいです。
球状のオブジェクトをマウスで移動させ、左ドラッグしている間、元石が削られるようなものです。
まずは当たり判定したオブジェクトのメッシュ変形をさせるようなものを考えているのですが、なかなか参考になるようなページが見つかりません。
(ブーリアン演算ではなかなかゲーム中リアルタイムに行っているものがなく、またステンシルバッファのようなものを利用したとしても、オブジェクトが削られた後、さらに削っていく、といったプロセスが行えず、やはりメッシュをだんだんと変形させていくしかないかと思っています)。
いちばんイメージに近いものではterrainに洞窟を掘るようなものですが、やはりこちらもゲーム中に操作で穴を掘る、というよりも洞窟のあるシーンを作る、というものでゲーム自体が削るものではありません。
抽象的な質問で申し訳ございませんが、なるべく多くのヒントを頂きたいと思っています。
ご参考になるようなC#スクリプト(例えば削る側/削られる側にアタッチするもの)や、参考になるページやアセットなど、幅広くアドバイス頂きたいです。よろしくお願い致します。
発生している問題・エラーメッセージ
エラーメッセージ
該当のソースコード
ソースコード
試したこと
ここに問題に対して試したことを記載してください。
ステンシルバッファ
補足情報(FW/ツールのバージョンなど)
ここにより詳細な情報を記載してください。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答2件
0
SkinnedMeshRendererで表現できないかと思い、下記のようなスクリプトを試してみました。
C#
1using System.Collections.Generic; 2using System.Linq; 3using UnityEngine; 4#if UNITY_EDITOR 5using UnityEditor; 6#endif 7 8public class ClayCuboid : MonoBehaviour 9{ 10 [SerializeField] private Material material; 11 [SerializeField][Range(1.0f, 32.0f)] private float verticesPerUnitHint = 16.0f; 12 [SerializeField][Range(1.0f, 8.0f)] private float controlPointsPerUnitHint = 4.0f; 13 [SerializeField][Range(0.0f, 256.0f)] private float controlPointDrag = 32.0f; 14 [SerializeField][Range(0.0f, 256.0f)] private float density = 16.0f; 15 [SerializeField] private bool useGravity; 16 17 private void Awake() 18 { 19 // 現在のスケールを粘土ブロックの大きさを示すものとして... 20 var size = this.transform.localScale; 21 22 // X、Y、Z方向の頂点数、コントロールポイント数を決める 23 var vertexCounts = Vector3Int.Max(Vector3Int.CeilToInt(size * this.verticesPerUnitHint), Vector3Int.one); 24 var controlPointCounts = Vector3Int.Max(Vector3Int.CeilToInt(size * this.controlPointsPerUnitHint), Vector3Int.one); 25 var controlPointIntervals = new Vector3( 26 size.x / controlPointCounts.x, 27 size.y / controlPointCounts.y, 28 size.z / controlPointCounts.z); 29 30 // コントロールポイントの半径は、さしあたり互いに重ならない最大量とする 31 var controlPointRadius = Mathf.Min( 32 Mathf.Min(controlPointIntervals.x, controlPointIntervals.y), 33 controlPointIntervals.z) * 0.5f; 34 35 // オブジェクト自体のスケールは(1, 1, 1)に戻し... 36 this.transform.localScale = Vector3.one; 37 38 // 粘土ブロックの表面下にコントロールポイントを並べる 39 var controlPoints = new List<Transform>(); 40 var controlPointOffset = (controlPointIntervals - size) * 0.5f; 41 for (var k = 0; k < controlPointCounts.z; k++) 42 { 43 for (var j = 0; j < controlPointCounts.y; j++) 44 { 45 for (var i = 0; i < controlPointCounts.x; i++) 46 { 47 if ((i != 0) && (i != (controlPointCounts.x - 1)) && 48 (j != 0) && (j != (controlPointCounts.y - 1)) && 49 (k != 0) && (k != (controlPointCounts.z - 1))) 50 { 51 continue; 52 } 53 54 var controlPoint = new GameObject($"({i:D2}, {j:D2}, {k:D2})").transform; 55 controlPoints.Add(controlPoint); 56 controlPoint.SetParent(this.transform, false); 57 controlPoint.localPosition = Vector3.Scale(new Vector3(i, j, k), controlPointIntervals) + controlPointOffset; 58 } 59 } 60 } 61 62 // コントロールポイントにSphereCollider、Rigidbodyをアタッチする 63 var controlPointMass = (this.density * size.x * size.y * size.z) / controlPoints.Count; 64 foreach (var controlPoint in controlPoints) 65 { 66 var controlPointCollider = controlPoint.gameObject.AddComponent<SphereCollider>(); 67 controlPointCollider.radius = controlPointRadius; 68 var controlPointRigidbody = controlPoint.gameObject.AddComponent<Rigidbody>(); 69 controlPointRigidbody.drag = this.controlPointDrag; 70 controlPointRigidbody.angularDrag = this.controlPointDrag; 71 controlPointRigidbody.mass = controlPointMass; 72 controlPointRigidbody.useGravity = this.useGravity; 73 } 74 75 // メッシュを作成する 76 var vertices = new List<Vector3>(); 77 var uvs = new List<Vector2>(); 78 var indices = new List<int>(); 79 var halfExtents = size * 0.5f; 80 CreateFace( 81 vertices, uvs, indices, 82 -halfExtents, 83 Vector3.right, Vector3.up, 84 new Vector2(size.x, size.y), 85 new Vector2Int(vertexCounts.x, vertexCounts.y)); 86 CreateFace( 87 vertices, uvs, indices, 88 -halfExtents, 89 Vector3.up, Vector3.forward, 90 new Vector2(size.y, size.z), 91 new Vector2Int(vertexCounts.y, vertexCounts.z)); 92 CreateFace( 93 vertices, uvs, indices, 94 -halfExtents, 95 Vector3.forward, Vector3.right, 96 new Vector2(size.z, size.x), 97 new Vector2Int(vertexCounts.z, vertexCounts.x)); 98 CreateFace( 99 vertices, uvs, indices, 100 halfExtents, 101 -Vector3.right, -Vector3.up, 102 new Vector2(size.x, size.y), 103 new Vector2Int(vertexCounts.x, vertexCounts.y), 104 true); 105 CreateFace( 106 vertices, uvs, indices, 107 halfExtents, 108 -Vector3.up, -Vector3.forward, 109 new Vector2(size.y, size.z), 110 new Vector2Int(vertexCounts.y, vertexCounts.z), 111 true); 112 CreateFace( 113 vertices, uvs, indices, 114 halfExtents, 115 -Vector3.forward, -Vector3.right, 116 new Vector2(size.z, size.x), 117 new Vector2Int(vertexCounts.z, vertexCounts.x), 118 true); 119 var parentToLocal = Matrix4x4.identity; 120 var weights = new BoneWeight1[4]; 121 var mesh = new Mesh 122 { 123 name = "Clay Cuboid", 124 vertices = vertices.ToArray(), 125 uv = uvs.ToArray(), 126 triangles = indices.ToArray(), 127 bindposes = controlPoints.Select( 128 c => 129 { 130 Debug.Assert(Matrix4x4.Inverse3DAffine(Matrix4x4.TRS(c.localPosition, c.localRotation, c.localScale), ref parentToLocal)); 131 return parentToLocal; 132 }).ToArray(), 133 // 頂点に対して最大4個のコントロールポイントが影響する 134 // 影響率はさしあたりコントロールポイントへの距離の2乗に反比例させた 135 boneWeights = vertices.Select( 136 v => 137 { 138 for (var i = 0; i < weights.Length; i++) 139 { 140 weights[i].boneIndex = 0; 141 weights[i].weight = float.PositiveInfinity; 142 } 143 144 var j = 0; 145 foreach (var sqrDistancesAndIndex in controlPoints.Select((c, i) => ((c.localPosition - v).sqrMagnitude, i)).OrderBy(ci => ci.Item1).Take(weights.Length)) 146 { 147 weights[j].boneIndex = sqrDistancesAndIndex.Item2; 148 weights[j].weight = sqrDistancesAndIndex.Item1; 149 j++; 150 } 151 152 var weightSum = 0.0f; 153 for (var i = 0; i < weights.Length; i++) 154 { 155 weights[i].weight = 1.0f / weights[i].weight; 156 weightSum += weights[i].weight; 157 } 158 159 var boneWeight = new BoneWeight(); 160 if (weightSum > 0.0f) 161 { 162 boneWeight.boneIndex0 = weights[0].boneIndex; 163 boneWeight.boneIndex1 = weights[1].boneIndex; 164 boneWeight.boneIndex2 = weights[2].boneIndex; 165 boneWeight.boneIndex3 = weights[3].boneIndex; 166 boneWeight.weight0 = weights[0].weight / weightSum; 167 boneWeight.weight1 = weights[1].weight / weightSum; 168 boneWeight.weight2 = weights[2].weight / weightSum; 169 boneWeight.weight3 = weights[3].weight / weightSum; 170 } 171 172 return boneWeight; 173 }).ToArray() 174 }; 175 mesh.RecalculateNormals(); 176 mesh.RecalculateTangents(); 177 178 // レンダラーを作成しメッシュをセットする 179 var meshRenderer = this.gameObject.AddComponent<SkinnedMeshRenderer>(); 180 meshRenderer.sharedMesh = mesh; 181 meshRenderer.bones = controlPoints.ToArray(); 182 meshRenderer.sharedMaterial = this.material; 183 } 184 185 private static void CreateFace( 186 List<Vector3> vertices, 187 List<Vector2> uvs, 188 List<int> indices, 189 Vector3 origin, 190 Vector3 u, 191 Vector3 v, 192 Vector2 size, 193 Vector2Int divisions, 194 bool invert = false) 195 { 196 var indexOffset = vertices.Count; 197 vertices.AddRange( 198 Enumerable.Range(0, divisions.y + 1).SelectMany( 199 j => Enumerable.Range(0, divisions.x + 1).Select( 200 i => origin + (u * ((i * size.x) / divisions.x)) + (v * ((j * size.y) / divisions.y))))); 201 uvs.AddRange( 202 Enumerable.Range(0, divisions.y + 1).SelectMany( 203 j => Enumerable.Range(0, divisions.x + 1) 204 .Select(i => new Vector2((float)i / divisions.x, (float)j / divisions.y)))); 205 indices.AddRange( 206 Enumerable.Range(0, divisions.y).SelectMany( 207 j => Enumerable.Range(0, divisions.x) 208 .SelectMany( 209 i => 210 { 211 var i00 = i + (j * (divisions.x + 1)); 212 var i01 = i00 + 1; 213 var i10 = i00 + divisions.x + 1; 214 var i11 = i10 + 1; 215 i00 += indexOffset; 216 i01 += indexOffset; 217 i10 += indexOffset; 218 i11 += indexOffset; 219 return invert 220 ? new[] {i00, i01, i10, i11, i10, i01} 221 : new[] {i00, i10, i01, i11, i01, i10}; 222 }))); 223 } 224 225 #if UNITY_EDITOR 226 private void OnDrawGizmos() 227 { 228 if (EditorApplication.isPlaying) 229 { 230 return; 231 } 232 233 // シーン編集中の段階では、シーンビューに直方体を描画して 234 // オブジェクトの大きさや配置を確認しやすくする 235 Gizmos.color = new Color(0.8f, 0.2f, 0.0f); 236 Gizmos.matrix = this.transform.localToWorldMatrix; 237 Gizmos.DrawCube(Vector3.zero, Vector3.one); 238 } 239 240 [MenuItem("GameObject/3D Object/Clay Cuboid")] 241 public static void Create() 242 { 243 var newObject = new GameObject("Clay Cuboid"); 244 Undo.RegisterCreatedObjectUndo(newObject, "Create Clay Cuboid"); 245 var clayCuboid = Undo.AddComponent<ClayCuboid>(newObject); 246 var dummyObject = GameObject.CreatePrimitive(PrimitiveType.Cube); 247 clayCuboid.material = dummyObject.GetComponent<Renderer>().sharedMaterial; 248 DestroyImmediate(dummyObject); 249 } 250 #endif 251}
メニューからオブジェクトを作成し...
適当に伸縮させて配置すると下図のように見えます。
実行時には、下図のようにブロック表面に沿ってSphereColliderを持ったオブジェクトが配置されます。
SkinnedMeshRenderer
の皮をかぶせた状態でこれらオブジェクトが物理的相互作用によって動けば、メッシュがひずんで見えるんじゃないかと考えました。
まあそれなりの見た目になったんじゃないかと思いますが、このやり方では上図のように物体を貫通して穴を開けようとしても、トポロジー的に異なる形状にはなれないため表面が引き伸ばされるだけとなってしまいます。
また、粘土細工のように精密な変形をさせるにはもっと大量のコライダーを配置する必要がありそうで、重すぎて実用に耐えないかもしれません。スカルプトモデリングソフトのようなことをさせたいのでしたら、おそらく根本的に方針を変えなければならないような気がします。
この方式だと大ざっぱな変形しか対応困難でしょうが、Unityの物理シミュレーションシステムをそのまま使っているので、シーン上の物体と相互作用させてそれっぽく見せかけるのには向いていると思います。たとえばDrag
をもっと小さくし、Use Gravity
をオンにすると落下するようになり、地面と衝突して潰れてひしゃげます。
今回の例では個々のコントロールポイントが自由に動けますが、たとえば互いにジョイントで連結してある程度の復元力を持たせれば、ところてんの塊のようなプルンプルンした物体を作ることもできそうな気がします。
投稿2020/08/29 07:30
総合スコア10811
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2020/09/03 03:29