質問をすることでしか得られない、回答やアドバイスがある。

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

新規登録して質問してみよう
ただいま回答率
85.48%
Unity

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

Q&A

解決済

4回答

710閲覧

instantiateによるボール連結にメッシュの割り当て

DY2peace

総合スコア20

Unity

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

0グッド

2クリップ

投稿2021/08/03 13:41

編集2021/08/17 01:54

前提・実現したいこと

 以前こちらのサイトでご教示いただいた『instantiateを使ったボールの連結』
ですが、たびたび恐縮ですが、これに円筒状のメッシュをバインド(変形)させて、ロープ状にする方法をご教示いただけないでしょうか。

イメージ説明

試したこと

 別の質問でご教示いただいた
円筒状メッシュに曲げ形状を追従させたい
のスクリプトを参考にしながらロープ生成を試みたのですが、ボーンバインドする際、オブジェクトが4つ以上(?)ないと生成できないのかなと思い、行き詰ってしまいました。
今回、一つずつのボール発生になるため、こういった場合、どのように円筒状のメッシュをバインドさせればいいか、ご教示いただけると幸いです。

どなたかご存じの方、いらっしゃいましたら、よろしくお願いいたします。

###追記(2021年8月17日)
Shaderに以下のエラー表示が出ました。
イメージ説明
cgincファイルですが、パート2でご教示いただいたコードをメモ帳にコピペして、拡張子をcgincファイルにして
RopeShaderと同じフォルダに入れています。
イメージ説明
現在、実行すると、マジェンタ色の線がボールと結びついている状態です。
イメージ説明

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

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

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

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

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

guest

回答4

0

ベストアンサー

##スクリプト側で円筒メッシュを構築する案

こちらの場合はスクリプト上で円筒の形にしてしまいますので、ロープのマテリアルには一般的な3Dモデル用マテリアルを使うことができるかと思います。

lang

1using System.Collections; 2using System.Collections.Generic; 3using System.Linq; 4using System.Runtime.InteropServices; 5using Unity.Collections; 6using Unity.Jobs; 7using Unity.Mathematics; 8using UnityEngine; 9using UnityEngine.Rendering; 10 11[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] 12public class MeshGenerator2 : MonoBehaviour 13{ 14 private const int InitialNumberedBallCapacity = 16; 15 16 [SerializeField] private ChainGenerator2 generator; 17 [SerializeField][Min(0.0f)] private float radius = 0.5f; 18 [SerializeField][Range(3, 12)] private int divisions = 8; 19 [SerializeField][Min(1.0f)] private float segmentLength = 2.0f; 20 [SerializeField][Min(0.0f)] private float verticalTextureLength = 8.0f; 21 22 // メッシュの頂点の構造を定義 23 // 今度はわりと一般的な構造になっている 24 private readonly VertexAttributeDescriptor[] vertexLayout = 25 { 26 new VertexAttributeDescriptor(VertexAttribute.Position), 27 new VertexAttributeDescriptor(VertexAttribute.Normal), 28 new VertexAttributeDescriptor(VertexAttribute.Tangent, VertexAttributeFormat.Float32, 4), 29 new VertexAttributeDescriptor(VertexAttribute.TexCoord0, VertexAttributeFormat.Float32, 2) 30 }; 31 32 [StructLayout(LayoutKind.Sequential)] 33 private struct Vertex 34 { 35 public float3 position; 36 public float3 normal; 37 public float4 tangent; 38 public float2 uv; 39 } 40 41 [StructLayout(LayoutKind.Sequential)] 42 private struct ControlPoint 43 { 44 public float3 position; 45 public float3 forward; 46 public float3 right; 47 public float distance; 48 } 49 50 private Mesh mesh; 51 private NativeArray<Vertex> vertices; 52 private NativeArray<ControlPoint> controlPoints; 53 private NativeArray<float3> segmentNormalPrimitive; 54 private readonly List<int> indices = new List<int>(); 55 private int[] segmentIndexPrimitive; 56 private JobHandle vertexConstructionJob; 57 private int vertexCountPerBall; 58 private int tessellation; 59 60 private IEnumerator Start() 61 { 62 // コントロールポイント1つ当たりの頂点数 63 // 円筒の断面が8分割なら、45°ずつ9頂点を配置することになる 64 this.vertexCountPerBall = this.divisions + 1; 65 66 // 断面、および円筒側面の原型を構築しておく 67 this.segmentNormalPrimitive = new NativeArray<float3>( 68 Enumerable.Range(0, this.vertexCountPerBall).Select( 69 i => 70 { 71 math.sincos((2.0f * math.PI * i) / this.divisions, out var sine, out var cosine); 72 return new float3(cosine, sine, 0.0f); 73 }).ToArray(), 74 Allocator.Persistent); 75 var facePrimitive = new[] { 0, 1, -this.vertexCountPerBall, 1 - this.vertexCountPerBall, -this.vertexCountPerBall, 1 }; 76 this.segmentIndexPrimitive = Enumerable.Range(0, this.divisions).SelectMany(i => facePrimitive.Select(j => i + j)).ToArray(); 77 78 // 根元ボールが取得できるようになるまで待機する 79 yield return new WaitUntil(() => this.generator && this.generator.RootBall); 80 81 // 節の分割数を決める 82 this.tessellation = Mathf.CeilToInt(this.generator.interval / this.segmentLength); 83 84 // 計算を単純化するため、MeshGeneratorはワールド原点に配置する 85 this.transform.SetParent(null); 86 this.transform.localScale = Vector3.one; 87 this.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); 88 89 // メッシュを作成し、初期サイズに拡張する 90 this.mesh = new Mesh { name = "Rope" }; 91 this.mesh.MarkDynamic(); 92 this.vertices = new NativeArray<Vertex>(this.vertexCountPerBall, Allocator.Persistent); 93 this.ExtendMesh(InitialNumberedBallCapacity); 94 this.GetComponent<MeshFilter>().mesh = this.mesh; 95 96 // 毎フレームメッシュを更新する 97 while (true) 98 { 99 yield return null; 100 101 this.UpdateMesh(); 102 } 103 } 104 105 private void OnDestroy() 106 { 107 this.segmentNormalPrimitive.Dispose(this.vertexConstructionJob); 108 this.vertices.Dispose(this.vertexConstructionJob); 109 if (this.controlPoints.IsCreated) 110 { 111 this.controlPoints.Dispose(this.vertexConstructionJob); 112 } 113 Destroy(this.mesh); 114 } 115 116 private void ExtendMesh(int size) 117 { 118 var indexOrigin = this.vertices.Length; 119 var oldVertices = this.vertices; 120 this.vertices = new NativeArray<Vertex>(indexOrigin + (size * this.vertexCountPerBall), Allocator.Persistent); 121 var oldSlice = new NativeSlice<Vertex>(oldVertices); 122 var newSlice = new NativeSlice<Vertex>(this.vertices, 0, oldVertices.Length); 123 newSlice.CopyFrom(oldSlice); 124 var lastVertex = oldVertices[oldVertices.Length - 1]; 125 for (var i = oldVertices.Length; i < this.vertices.Length; i++) 126 { 127 this.vertices[i] = lastVertex; 128 } 129 oldVertices.Dispose(this.vertexConstructionJob); 130 for (var i = 0; i < size; i++) 131 { 132 this.indices.AddRange(this.segmentIndexPrimitive.Select(j => indexOrigin + (i * this.vertexCountPerBall) + j)); 133 } 134 this.mesh.SetVertexBufferParams(this.vertices.Length, this.vertexLayout); 135 this.mesh.SetVertexBufferData(this.vertices, 0, 0, this.vertices.Length); 136 this.mesh.SetIndices(this.indices, MeshTopology.Triangles, 0, false); 137 } 138 139 private void UpdateMesh() 140 { 141 // まず、前フレームに開始したメッシュ構築処理が完了するのを 142 // 待ってから、メッシュに頂点データをセットする 143 // 処理効率化のためにこのようにしたが、現フレームの描画は 144 // 前フレームのボール位置に基づいているため、1フレームだけ 145 // 描画が遅れてしまう欠点がある 146 this.vertexConstructionJob.Complete(); 147 this.mesh.SetVertexBufferData(this.vertices, 0, 0, this.vertices.Length); 148 this.mesh.RecalculateBounds(); 149 150 // 引き続き、次フレームのためのメッシュ構築を始める 151 // まず現在のボールの数に合わせてメッシュを拡張する 152 var currentNodeCapacity = this.vertices.Length / this.vertexCountPerBall; 153 var currentBallCount = this.generator.BallCount; 154 var currentNodeCount = ((currentBallCount - 1) * this.tessellation) + 1; 155 if (currentNodeCount > currentNodeCapacity) 156 { 157 this.ExtendMesh(currentNodeCapacity - 1); 158 } 159 160 // 現在の各ボールの位置と姿勢を取得し... 161 if (this.controlPoints.IsCreated) 162 { 163 this.controlPoints.Dispose(); 164 } 165 this.controlPoints = new NativeArray<ControlPoint>(currentNodeCount, Allocator.TempJob, NativeArrayOptions.UninitializedMemory); 166 var i = 0; 167 foreach (var (position, forward, right, distance) in this.generator.BallPositions) 168 { 169 this.controlPoints[i] = new ControlPoint 170 { 171 position = position, 172 forward = forward, 173 right = right, 174 distance = distance 175 }; 176 i += this.tessellation; 177 } 178 179 // 軸に沿った折れ線の分割と... 180 var tessellationJob = new TessellationJob 181 { 182 Tessellation = this.tessellation, 183 Nodes = this.controlPoints 184 }.Schedule(currentBallCount - 1, 0); 185 186 // 円筒断面の配置を予約し... 187 this.vertexConstructionJob = new FacingJob 188 { 189 Divisions = this.divisions, 190 Radius = this.radius, 191 TextureLength = this.verticalTextureLength, 192 NormalPrimitive = this.segmentNormalPrimitive, 193 ControlPoints = this.controlPoints, 194 Vertices = this.vertices 195 }.Schedule(this.vertices.Length / this.vertexCountPerBall, 0, tessellationJob); 196 197 // 予約したジョブを始動する 198 // このまま裏方でメッシュデータ作りを続けてもらい、 199 // 次フレームの頭で結果を受け取ることになる 200 JobHandle.ScheduleBatchedJobs(); 201 } 202 203 // 折れ線分割処理 204 private struct TessellationJob : IJobParallelFor 205 { 206 public int Tessellation; 207 [NativeDisableParallelForRestriction] public NativeArray<ControlPoint> Nodes; 208 209 public void Execute(int index) 210 { 211 var index0 = index * this.Tessellation; 212 var index1 = index0 + this.Tessellation; 213 var node0 = this.Nodes[index0]; 214 var node1 = this.Nodes[index1]; 215 var p0 = node0.position; 216 var p1 = node1.position; 217 var f0 = node0.forward; 218 var f1 = node1.forward; 219 var l = math.distance(p0, p1) / 3.0f; 220 var cp0 = p0 - (f0 * l); 221 var cp1 = p1 + (f1 * l); 222 for (var i = 1; i < this.Tessellation; i++) 223 { 224 var t = new float2((float)i / this.Tessellation, 0.0f); 225 t.y = 1.0f - t.x; 226 var t2 = t * t; 227 this.Nodes[index0 + i] = new ControlPoint 228 { 229 position = (t.y * t2.y * p0) + (3.0f * t.x * t2.y * cp0) + (3.0f * t.y * t2.x * cp1) + (t.x * t2.x * p1), 230 forward = math.normalize(math.lerp(node0.forward, node1.forward, t.x)), 231 right = math.normalize(math.lerp(node0.right, node1.right, t.x)), 232 distance = math.lerp(node0.distance, node1.distance, t.x) 233 }; 234 } 235 } 236 } 237 238 // 円筒断面配置処理 239 private struct FacingJob : IJobParallelFor 240 { 241 public int Divisions; 242 public float Radius; 243 public float TextureLength; 244 [ReadOnly] public NativeArray<float3> NormalPrimitive; 245 [ReadOnly] public NativeArray<ControlPoint> ControlPoints; 246 [NativeDisableParallelForRestriction][WriteOnly] public NativeArray<Vertex> Vertices; 247 248 public void Execute(int index) 249 { 250 var o = index * (this.Divisions + 1); 251 var controlPoint = this.ControlPoints[math.min(index, this.ControlPoints.Length - 1)]; 252 var center = controlPoint.position; 253 var rotation = new float3x3(controlPoint.right, math.cross(controlPoint.forward, controlPoint.right), controlPoint.forward); 254 var v = controlPoint.distance / this.TextureLength; 255 for (var i = 0; i <= this.Divisions; i++) 256 { 257 var localNormal = this.NormalPrimitive[i]; 258 var localTangent = new float3(-localNormal.y, localNormal.x, 0.0f); 259 var worldNormal = math.mul(rotation, localNormal); 260 var worldTangent = new float4(math.mul(rotation, localTangent), -1.0f); 261 this.Vertices[o + i] = new Vertex 262 { 263 position = (worldNormal * this.Radius) + center, 264 normal = worldNormal, 265 tangent = worldTangent, 266 uv = new float2((float)i / this.Divisions, v) 267 }; 268 } 269 } 270 } 271}

投稿2021/08/18 10:04

Bongo

総合スコア10807

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

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

0

パート3

本体の「Rope.shader」で構成されています。

ShaderLab

1Shader "Custom/Rope" 2{ 3 Properties 4 { 5 _Color ("Color", Color) = (1.0, 1.0, 1.0, 1.0) 6 _MainTex ("Albedo", 2D) = "white" {} 7 [Space(16)] 8 _Glossiness ("Smoothness", Range(0.0, 1.0)) = 0.5 9 [Gamma] _Metallic ("Metallic", Range(0.0, 1.0)) = 0.0 10 [Space(16)] 11 [Toggle(_METALLICGLOSSMAP)] _UseMetallicGlossMap ("Use Metallic / Smoothness Map", Float) = 0.0 12 _GlossMapScale ("Smoothness Map Scale", Range(0.0, 1.0)) = 1.0 13 [NoScaleOffset] _MetallicGlossMap ("Metallic / Smoothness Map", 2D) = "white" {} 14 [Space(16)] 15 [ToggleOff] _SpecularHighlights ("Specular Highlights", Float) = 1.0 16 [ToggleOff] _GlossyReflections ("Glossy Reflections", Float) = 1.0 17 [Space(16)] 18 _BumpScale ("Normal Scale", Float) = 1.0 19 [NoScaleOffset][Normal] _BumpMap ("Normal Map", 2D) = "bump" {} 20 [Space(16)] 21 _OcclusionStrength ("Occlusion Strength", Range(0.0, 1.0)) = 1.0 22 [NoScaleOffset] _OcclusionMap ("Occlusion Map", 2D) = "white" {} 23 [Space(16)] 24 [Toggle(_EMISSION)] _UseEmission ("Use Emission", Float) = 0.0 25 _EmissionColor ("Color", Color) = (0.0, 0.0, 0.0) 26 [NoScaleOffset] _EmissionMap ("Emission", 2D) = "white" {} 27 } 28 29 CGINCLUDE 30 #define UNITY_SETUP_BRDF_INPUT MetallicSetup 31 ENDCG 32 33 SubShader 34 { 35 Tags { "RenderType"="Opaque" "PerformanceChecks"="False" } 36 Pass 37 { 38 Name "FORWARD" 39 Tags { "LightMode" = "ForwardBase" } 40 Blend One Zero 41 ZWrite On 42 CGPROGRAM 43 #define _NORMALMAP 1 44 #pragma shader_feature_fragment _EMISSION 45 #pragma shader_feature_local _METALLICGLOSSMAP 46 #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF 47 #pragma shader_feature_local_fragment _GLOSSYREFLECTIONS_OFF 48 #pragma multi_compile_fwdbase 49 #pragma multi_compile_fog 50 #pragma skip_variants DYNAMICLIGHTMAP_ON 51 #pragma skip_variants DIRLIGHTMAP_COMBINED 52 #pragma require tessellation tessHW 53 #pragma require geometry 54 #pragma vertex vert 55 #pragma hull hull 56 #pragma domain domain 57 #pragma geometry geom 58 #pragma fragment fragBase 59 #include "UnityStandardCoreForward.cginc" 60 #include "Rope.cginc" 61 [maxvertexcount((MAX_DIVISIONS + 1) * 2)] 62 void geom(line controlPoint ic[2], inout 63 #if UNITY_STANDARD_SIMPLE 64 TriangleStream<VertexOutputBaseSimple> 65 #else 66 TriangleStream<VertexOutputForwardBase> 67 #endif 68 stream) 69 { 70 VertexInput v0, v1; 71 for (int i = 0; i <= _Divisions; i++) 72 { 73 composeVertexInput(i, ic, v0, v1); 74 stream.Append(vertBase(v0)); 75 stream.Append(vertBase(v1)); 76 } 77 } 78 ENDCG 79 } 80 Pass 81 { 82 Name "FORWARD_DELTA" 83 Tags { "LightMode" = "ForwardAdd" } 84 Blend One One 85 Fog { Color (0,0,0,0) } 86 ZWrite Off 87 ZTest LEqual 88 CGPROGRAM 89 #define _NORMALMAP 1 90 #pragma shader_feature_local _METALLICGLOSSMAP 91 #pragma shader_feature_local_fragment _SPECULARHIGHLIGHTS_OFF 92 #pragma multi_compile_fwdadd_fullshadows 93 #pragma multi_compile_fog 94 #pragma skip_variants DYNAMICLIGHTMAP_ON 95 #pragma skip_variants DIRLIGHTMAP_COMBINED 96 #pragma require tessellation tessHW 97 #pragma require geometry 98 #pragma vertex vert 99 #pragma hull hull 100 #pragma domain domain 101 #pragma geometry geom 102 #pragma fragment fragAdd 103 #include "UnityStandardCoreForward.cginc" 104 #include "Rope.cginc" 105 [maxvertexcount((MAX_DIVISIONS + 1) * 2)] 106 void geom(line controlPoint ic[2], inout 107 #if UNITY_STANDARD_SIMPLE 108 TriangleStream<VertexOutputForwardAddSimple> 109 #else 110 TriangleStream<VertexOutputForwardAdd> 111 #endif 112 stream) 113 { 114 VertexInput v0, v1; 115 for (int i = 0; i <= _Divisions; i++) 116 { 117 composeVertexInput(i, ic, v0, v1); 118 stream.Append(vertAdd(v0)); 119 stream.Append(vertAdd(v1)); 120 } 121 } 122 ENDCG 123 } 124 Pass 125 { 126 Name "ShadowCaster" 127 Tags { "LightMode" = "ShadowCaster" } 128 ZWrite On ZTest LEqual 129 CGPROGRAM 130 #pragma shader_feature_local _METALLICGLOSSMAP 131 #pragma multi_compile_shadowcaster 132 #pragma require tessellation tessHW 133 #pragma require geometry 134 #pragma vertex vert 135 #pragma hull hull 136 #pragma domain domain 137 #pragma geometry geom 138 #pragma fragment fragShadowCaster 139 #include "UnityStandardShadow.cginc" 140 #include "Rope.cginc" 141 struct shadowOutput 142 { 143 float4 opos : SV_POSITION; 144 V2F_SHADOW_CASTER_NOPOS 145 float2 tex : TEXCOORD1; 146 UNITY_VERTEX_OUTPUT_STEREO 147 }; 148 [maxvertexcount((MAX_DIVISIONS + 1) * 2)] 149 void geom(line controlPoint ic[2], inout TriangleStream<shadowOutput> stream) 150 { 151 VertexInput v0, v1; 152 shadowOutput so; 153 UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(so); 154 for (int i = 0; i <= _Divisions; i++) 155 { 156 composeVertexInput(i, ic, v0, v1); 157 #if defined(SHADOWS_CUBE) && !defined(SHADOWS_CUBE_IN_DEPTH_TEX) 158 so.vec = v0.vertex.xyz - _LightPositionRange.xyz; 159 so.opos = UnityObjectToClipPos(v0.vertex.xyz); 160 so.tex = TRANSFORM_TEX(v0.uv0, _MainTex); 161 stream.Append(so); 162 so.vec = v1.vertex.xyz - _LightPositionRange.xyz; 163 so.opos = UnityObjectToClipPos(v1.vertex.xyz); 164 so.tex = TRANSFORM_TEX(v1.uv0, _MainTex); 165 stream.Append(so); 166 #else 167 so.opos = UnityClipSpaceShadowCasterPos(v0.vertex, v0.normal); 168 so.opos = UnityApplyLinearShadowBias(so.opos); 169 so.tex = TRANSFORM_TEX(v0.uv0, _MainTex); 170 stream.Append(so); 171 so.opos = UnityClipSpaceShadowCasterPos(v1.vertex, v1.normal); 172 so.opos = UnityApplyLinearShadowBias(so.opos); 173 so.tex = TRANSFORM_TEX(v1.uv0, _MainTex); 174 stream.Append(so); 175 #endif 176 } 177 } 178 ENDCG 179 } 180 } 181}

レンダリング結果は下図のようになりました。

図1

メッシュ自体はボールの中心をつなぐ折れ線であり、シェーダー上で円筒の形を生成するスタイルにしてみました。また、ボールの間隔を広げると本来はロープの節が長く伸びていきますが...

図2

折れ線を細分割し曲線に沿わせることで、ロープの節を増やしてなめらかに見えるようにしています。

図3

ですが、代償としてシェーダーのターゲットレベルが引き上げられてしまっています。「シェーダーコンパイルターゲットレベル - Unity マニュアル」には...

一般に言う #pragma target ディレクティブは上の要件を簡略にしたものであり、以下に対応します。

  • 2.5: derivatives
  • 3.0: 2.5 + interpolators10 + samplelod + fragcoord
  • 3.5: 3.0 + interpolators15 + mrt4 + integers + 2darray + instancing
  • 4.0: 3.5 + geometry
  • 5.0: 4.0 + compute + randomwrite + tesshw + tessellation
  • 4.5: 3.5 + compute + randomwrite
  • 4.6: 4.0 + cubearray + tesshw + tessellation

とあります。
今回のシェーダーでは、メッシュ自体のデータ量をなるべく削減してメッシュ構築処理をシェーダー側に持っていくことで、頻繁なボール増減にかかる負荷を削減することを狙ってみました。ですがシェーダーでtesshwtessellationgeometryを使用しているため、要求レベルは5.0になってしまうかと思います。
要求レベルを引き下げたい場合は、シェーダー側はもっとシンプルにして、細分割や円筒構築をスクリプト側で行う必要があるでしょう。処理コストは大きくなるだろうと思いますが、そもそも描画できないのでは話にならないでしょうから、もしレベル引き下げが必要でしたらコメントいただければ検討してみようと思います。

インスペクターの様子

シーン上のオブジェクト

図4

ロープマテリアル

図5

ロープシェーダー

図6

投稿2021/08/08 23:02

編集2021/08/16 21:49
Bongo

総合スコア10807

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

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

DY2peace

2021/08/10 02:54

いつもありがとうございます。お手数をおかけいたします。 現在、実装を試みているのですが、MeshGeneratorの「SetInteger」のところでエラーが出てしまいました。 「'Material' に 'SetInteger' の定義が含まれておらず、型 'Material' の最初の引数を受け付けるアクセス可能な拡張メソッド 'SetInteger' が見つかりませんでした。using ディレクティブまたはアセンブリ参照が不足していないことを確認してください [Assembly-CSharp]」 先にMaterialに割り当てておく、もしくはまだShaderを作ってないのですが、これが原因でしょうか。 ご迷惑をおかけいたしますがよろしくお願いいたします。
Bongo

2021/08/10 03:12

すみません、先ほどリリースノート(https://unity3d.com/jp/unity/whats-new/2021.1.0 )を確認したところ... Shaders: Deprecated: Material, MaterialPropertyBlock and Shader API for working with "Int" properties is deprecated. Use "Integer" or "Float" properties and the corresponding API instead. との記述がありました。SetIntegerは2021以降からのようですね。機能面では従来のSetIntと違いはないように見えますので、SetIntegerをSetIntに書き換えていただけますでしょうか。
DY2peace

2021/08/14 12:11

ご回答ありがとうございます。 お恥ずかしながら、Shaderに関しては、使ったことがなく、アタッチの仕方など勉強しながら今取り組んでおります。 少々お時間いただけると幸いです。
DY2peace

2021/08/16 13:50

申し訳ございません。色々と調べてみてもわからなかったので、 大変お手数ですが、各C#スクリプト、Shaderがどこに配置されているか、Unity画面のヒエラルキーを見せていただいてご教示いただけないでしょうか。
Bongo

2021/08/16 21:50

私の場合のシーンの様子を追記いたしました。シェーダーのたぐいに不慣れだそうですが、使うこと自体については特別な点はないと思うんですがね... 今のところ気になりますのは、やはり回答中で申し上げましたようにシェーダー自体が動作しない可能性ですかね。プロジェクト内にあるシェーダーファイルを選択した時のインスペクターの表示(追記した図のうち一番最後)に、何かエラーメッセージが出ていたりはしませんでしょうか?
DY2peace

2021/08/17 02:03

動画のご提示いただきありがとうございました。MeshGeneratorの部分が誤解していたので、無事修正できました。  上述の「本日分の追記」にて、記載しましたが、作成したRopeShaderに『Couldn’t open include file 'Rope.cginc'』というエラーが3行ほど出てしまいます。  Assetsフォルダーに直接cgincファイルを置いてしまっているのですが、適切な置き場などがあるのでしょうか。 的外れなご質問していたら申し訳ございません。
DY2peace

2021/08/17 02:15

たびたび申し訳ございません。やはりcgincのファイルの置き場の問題でした。 Rope.Shaderのスクリプトを #include "../Assets/Rope.cginc" と記載したら、3つのエラーは表示されませんでした。 現状では、テクスチャが反映されず、真っ白なロープが出てきてしまってますので、もう少し条件を確認してみます。(エラーは今のところ何も出てないです)
DY2peace

2021/08/18 07:29

色々とお手数をおかけして申し訳ございませんでした。またご対応いただきありがとうございます。 上述のテクスチャの件も含めて、スケール調整などを実施して、無事反映することができました。 いったんここで、ベストアンサーと高評価押させていただいてもよろしいでしょうか。
Bongo

2021/08/18 10:05

うまくいきましたか、気がかりだったもので安心しました。特に問題がなさそうでしたら本トピックはクローズしてしまってかまわないかと思います。クローズ後でも、teratailの場合は必要に応じて回答投稿・追記修正が可能ですので、気になる点などありましたらコメントいただければ返信いたします。 実は、以前のコメントで申し上げたように環境の違いのせいでシェーダーが動作していないのでは...と気になっていまして、代替案の構想も練っているところでした。結局使わずに済んでけっこうなことではありますが、せっかくですので追記いたしました。もしシェーダー方式で新しい問題が起こったり、あるいはマテリアルをもっと自由に選びたいといったことがありましたら、あちらの方式を試してみてもいいかもしれません。
guest

0

パート2

シーン上にもう一つ空のオブジェクトを用意して、それに下記スクリプトをアタッチしレンダリング担当としました。

lang

1using System.Collections; 2using System.Collections.Generic; 3using System.Runtime.InteropServices; 4using UnityEngine; 5using UnityEngine.Rendering; 6 7// シーン上に空のオブジェクトを作ってMeshGeneratorをアタッチすると、自動的にMeshFilterと 8// MeshRendererも付いてくるはずなので、MeshRendererに後述のロープ用マテリアルをセットしておく 9[RequireComponent(typeof(MeshFilter), typeof(MeshRenderer))] 10public class MeshGenerator : MonoBehaviour 11{ 12 private const int InitialNumberedBallCapacity = 16; 13 14 private static readonly int RadiusProperty = Shader.PropertyToID("_Radius"); 15 private static readonly int DivisionsProperty = Shader.PropertyToID("_Divisions"); 16 private static readonly int SegmentLengthProperty = Shader.PropertyToID("_SegmentLength"); 17 private static readonly int VerticalTextureLengthProperty = Shader.PropertyToID("_VerticalTextureLength"); 18 private static readonly int BallCountProperty = Shader.PropertyToID("_BallCount"); 19 private static readonly int RootPositionProperty = Shader.PropertyToID("_RootPosition"); 20 private static readonly int RootForwardProperty = Shader.PropertyToID("_RootForward"); 21 private static readonly int RootRightProperty = Shader.PropertyToID("_RootRight"); 22 23 [SerializeField] private ChainGenerator2 generator; // 前述のボール生成スクリプト(インスペクター上でセットしておく) 24 [SerializeField][Min(0.0f)] private float radius = 0.5f; // ロープの断面の半径 25 [SerializeField][Range(3, 12)] private int divisions = 8; // ロープの断面の分割数 26 [SerializeField][Min(1.0f)] private float segmentLength = 2.0f; // ロープの方向に沿ったメッシュの節の最大の長さ 27 [SerializeField][Min(0.0f)] private float verticalTextureLength = 8.0f; // ロープの方向に沿ったテクスチャの1周期の長さ 28 29 // メッシュの頂点の構造を定義 30 // 1つのfloat4と2つのfloat3で構成されており、それらにPOSITION、TEXCOORD0、TEXCOORD1セマンティクスを割り当てる 31 private readonly VertexAttributeDescriptor[] vertexLayout = 32 { 33 new VertexAttributeDescriptor(VertexAttribute.Position, VertexAttributeFormat.Float32, 4), 34 new VertexAttributeDescriptor(VertexAttribute.TexCoord0), 35 new VertexAttributeDescriptor(VertexAttribute.TexCoord1) 36 }; 37 38 // MeshGenerator側から見た頂点の構造を定義 39 [StructLayout(LayoutKind.Sequential)] 40 private struct Vertex 41 { 42 public Vector3 position; // ボールの位置(つまり断面の円の中心のワールド座標) 43 public float axialPosition; // 先端からのロープに沿った位置 44 public Vector3 forward; // ボールのtransform.forward 45 public Vector3 right; // ボールのtransform.right 46 } 47 48 private readonly List<Vertex> vertices = new List<Vertex>(); 49 private readonly List<int> indices = new List<int>(); 50 private Mesh mesh; 51 private Material material; 52 53 private IEnumerator Start() 54 { 55 // 根元ボールが取得できるようになるまで待機する 56 yield return new WaitUntil(() => this.generator && this.generator.RootBall); 57 58 // 計算を単純化するため、MeshGeneratorはワールド原点に配置する 59 this.transform.SetParent(null); 60 this.transform.localScale = Vector3.one; 61 this.transform.SetPositionAndRotation(Vector3.zero, Quaternion.identity); 62 63 // レンダラーからマテリアルを取得し、恒常的な設定項目をセットする 64 this.material = this.GetComponent<MeshRenderer>().material; 65 this.material.SetFloat(RadiusProperty, this.radius); 66 this.material.SetInteger(DivisionsProperty, this.divisions); 67 this.material.SetFloat(SegmentLengthProperty, this.segmentLength); 68 this.material.SetFloat(VerticalTextureLengthProperty, this.verticalTextureLength); 69 this.material.SetVector(RootPositionProperty, this.generator.RootBall.position); 70 this.material.SetVector(RootForwardProperty, this.generator.RootBall.transform.forward); 71 this.material.SetVector(RootRightProperty, this.generator.RootBall.transform.right); 72 73 // メッシュを作成し、初期サイズに拡張する 74 this.mesh = new Mesh { name = "Rope" }; 75 this.mesh.MarkDynamic(); 76 this.vertices.Add(new Vertex()); 77 this.ExtendMesh(InitialNumberedBallCapacity); 78 this.GetComponent<MeshFilter>().mesh = this.mesh; 79 80 // 毎フレームメッシュを更新する 81 while (true) 82 { 83 yield return null; 84 85 this.UpdateMesh(); 86 } 87 } 88 89 private void OnDestroy() 90 { 91 Destroy(this.mesh); 92 Destroy(this.material); 93 } 94 95 private void ExtendMesh(int size) 96 { 97 var indexOrigin = this.vertices.Count; 98 var newVertexCount = indexOrigin + size; 99 for (var i = indexOrigin; i < newVertexCount; i++) 100 { 101 this.vertices.Add(new Vertex()); 102 this.indices.Add(i - 1); 103 this.indices.Add(i); 104 } 105 106 this.mesh.SetVertexBufferParams(this.vertices.Count, this.vertexLayout); 107 this.mesh.SetVertexBufferData(this.vertices, 0, 0, this.vertices.Count); 108 109 // 1本の折れ線なので、通常はトポロジーとしてLineStripが使えるはずだが 110 // テッセレーション機能を持つシェーダーとの併用ではなぜかレンダリングできず 111 // Linesを使用している 112 this.mesh.SetIndices(this.indices, MeshTopology.Lines, 0, false); 113 } 114 115 private void UpdateMesh() 116 { 117 // 現在のボールの数に合わせてメッシュを拡張する 118 var currentBallCapacity = this.vertices.Count; 119 var currentBallCount = this.generator.BallCount; 120 if (currentBallCount > currentBallCapacity) 121 { 122 this.ExtendMesh(currentBallCapacity - 1); 123 } 124 125 // 現在の各ボールの位置と姿勢を取得し... 126 var i = 0; 127 foreach (var (position, forward, right, distance) in this.generator.BallPositions) 128 { 129 var v = this.vertices[i]; 130 v.position = position; 131 v.axialPosition = distance; 132 v.forward = forward; 133 v.right = right; 134 this.vertices[i] = v; 135 i++; 136 } 137 138 // メッシュにセットして、マテリアルのボール個数プロパティも更新する 139 this.mesh.SetVertexBufferData(this.vertices, 0, 0, this.vertices.Count); 140 this.mesh.RecalculateBounds(); 141 this.material.SetInteger(BallCountProperty, currentBallCount); 142 } 143}

これにインスペクター上でシーン上のChainGenerator2をセットし、MeshRendererにはロープ用マテリアルをセットします。
ロープ用シェーダーは2つのファイルに分かれており、各パス共通の部分を記述した「Rope.cginc」と...

ShaderLab

1#ifndef ROPE_INCLUDED 2#define ROPE_INCLUDED 3 4#define MAX_DIVISIONS 12 5 6#include "UnityCG.cginc" 7#include "Tessellation.cginc" 8 9float _TessellationEdgeLength; 10float _Radius; 11int _Divisions; 12float _SegmentLength; 13float _VerticalTextureLength; 14uint _BallCount; 15float3 _RootPosition; 16float3 _RootForward; 17float3 _RootRight; 18 19struct appdata 20{ 21 float4 vertex : POSITION; 22 float3 forward : TEXCOORD0; 23 float3 right : TEXCOORD1; 24}; 25 26struct controlPoint 27{ 28 float4 position : WORLDPOS; 29 float3 forward : TEXCOORD0; 30 float3 right : TEXCOORD1; 31}; 32 33controlPoint vert(appdata v, uint i : SV_VertexID) 34{ 35 controlPoint oc; 36 if (i >= _BallCount) 37 { 38 oc.position = float4(_RootPosition, 0.0); 39 oc.forward = _RootForward; 40 oc.right = _RootRight; 41 } 42 else 43 { 44 oc.position = v.vertex; 45 oc.forward = v.forward; 46 oc.right = v.right; 47 } 48 return oc; 49} 50 51[domain("isoline")] 52[partitioning("fractional_odd")] 53[outputtopology("line")] 54[patchconstantfunc("patchConst")] 55[outputcontrolpoints(2)] 56controlPoint hull(InputPatch<controlPoint, 2> ic, uint i : SV_OutputcontrolPointID) 57{ 58 return ic[i]; 59} 60 61void patchConst(InputPatch<controlPoint, 2> ic, out float f[2] : SV_TessFactor) 62{ 63 f[0] = 1.0; 64 f[1] = max(distance(ic[0].position.xyz, ic[1].position.xyz) / _SegmentLength, 1.0); 65} 66 67[domain("isoline")] 68controlPoint domain(float f[2] : SV_TessFactor, const OutputPatch<controlPoint, 2> ic, float2 loc : SV_DomainLocation) 69{ 70 controlPoint oc; 71 float3 p0 = ic[0].position.xyz; 72 float3 p1 = ic[1].position.xyz; 73 float3 f0 = ic[0].forward; 74 float3 f1 = ic[1].forward; 75 float l = distance(p0, p1) / 3.0; 76 float3 cp0 = p0 - f0 * l; 77 float3 cp1 = p1 + f1 * l; 78 float2 t = float2(loc.x, 1.0 - loc.x); 79 float2 t2 = t * t; 80 oc.position = float4(t.y * t2.y * p0 + 3.0 * t.x * t2.y * cp0 + 3.0 * t.y * t2.x * cp1 + t.x * t2.x * p1, lerp(ic[0].position.w, ic[1].position.w, t.x)); 81 oc.forward = normalize(lerp(f0, f1, t.x)); 82 oc.right = normalize(lerp(ic[0].right, ic[1].right, t.x)); 83 return oc; 84} 85 86void composeVertexInput(int i, controlPoint ic[2], out VertexInput o0, out VertexInput o1) 87{ 88 float3 p0 = ic[0].position.xyz; 89 float3 p1 = ic[1].position.xyz; 90 float3x3 r0 = float3x3(ic[0].right, cross(ic[0].forward, ic[0].right), ic[0].forward); 91 float3x3 r1 = float3x3(ic[1].right, cross(ic[1].forward, ic[1].right), ic[1].forward); 92 float2 v = float2(ic[0].position.w, ic[1].position.w) / _VerticalTextureLength; 93 VertexInput o; 94 float u = float(i) / _Divisions; 95 float3 localNormal = 0.0; 96 sincos(2.0 * UNITY_PI * u, localNormal.y, localNormal.x); 97 float3 localTangent = float3(-localNormal.y, localNormal.x, 0.0); 98 float3 localPosition = localNormal * _Radius; 99 o.vertex = float4(mul(localPosition, r0) + p0, 1.0); 100 o.normal = mul(localNormal, r0); 101 o.uv0 = float2(u, v.x); 102 #ifdef UNITY_STANDARD_INPUT_INCLUDED 103 o.uv1 = o.uv0; 104 o.tangent = float4(mul(localTangent, r0), -1.0); 105 #endif 106 o0 = o; 107 o.vertex = float4(mul(localPosition, r1) + p1, 1.0); 108 o.normal = mul(localNormal, r1); 109 o.uv0 = float2(u, v.y); 110 #ifdef UNITY_STANDARD_INPUT_INCLUDED 111 o.uv1 = o.uv0; 112 o.tangent = float4(mul(localTangent, r1), -1.0); 113 #endif 114 o1 = o; 115} 116 117#endif // ROPE_INCLUDED

(パート3に続く...)

投稿2021/08/08 22:59

編集2021/08/09 00:16
Bongo

総合スコア10807

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

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

0

円筒状メッシュに曲げ形状を追従させたい」で例示したようなSkinnedMeshRendererを使う方法もあり得るかもしれませんが、今回のようにボーンの数が頻繁に増減する状況では、増減のたびに再バインドを行う必要がありそうです。
それだとさすがに高負荷なんじゃないかと思い、方針を変えて「メッシュの動的生成・更新」でいくことにしました。さいわいメッシュの形状は単純な円筒で済ませられそうですので、メッシュの構築も比較的シンプルに行えそうだと考えました。

まず、「instantiateを使ったボールの連結」の時に例示しましたスクリプトでは、末端の一部のボールだけを覚えておいてジョイントのつなぎ替えを行っていましたが、下記のようにちょっと書き足してすべてのボールの位置と姿勢を取得できるようにしました。書き足した部分に+記号を付けて表示しています。

diff

1+using System.Collections.Generic; 2+using System.Linq; 3using UnityEngine; 4 5public class ChainGenerator2 : MonoBehaviour 6{ 7 public GameObject jyakanBall; // ボールのプレハブ...すでにRigidbodyを持っていると想定 8 public GameObject firstBall; // FirstBallはあらかじめシーン上に存在していると想定 9 public Transform firstPosiBase; // ボール発射初期位置 10 public float speed = 10; // ボールの速度 11 public float interval = 2.0f; // ボールを設置する間隔 12 public float angularLimit = 6.0f; // ジョイントの曲がる角度の上限 13 public float spring = 100.0f; 14 public float damper = 10.0f; 15 16 private Rigidbody rootRigidbody; 17 private Rigidbody firstChildRigidbody; 18 private Rigidbody firstRigidbody; 19 private ConfigurableJoint primaryJoint; 20 private ConfigurableJoint secondaryJoint; 21 private Vector3 connectedAnchor; 22 private SoftJointLimit highLimit; 23 private SoftJointLimit lowLimit; 24 private JointDrive drive; 25 private bool isWinding; 26 27+ // 根元以外のボールを覚えておくためのスタック 28+ private readonly Stack<Rigidbody> numberedBalls = new Stack<Rigidbody>(); 29 30+ // 根元のボールを得るプロパティ 31+ public Rigidbody RootBall => this.rootRigidbody; 32 33+ // 現在のボールの数を得るプロパティ 34+ public int BallCount => this.numberedBalls.Count + 1; 35 36+ // 現在のボールの位置、前方向、右方向、firstChildRigidbodyを起点にした鎖上の位置を得るプロパティ 37+ // firstRigidbodyが先頭、rootRigidbodyが末尾 38+ public IEnumerable<(Vector3, Vector3, Vector3, float)> BallPositions => this.numberedBalls.Reverse() 39+ .Select(ball => ball.transform).Select((t, i) => (t.position, t.forward, t.right, i * this.interval)) 40+ .Concat(Enumerable.Repeat((this.rootRigidbody.position, this.rootRigidbody.transform.forward, this.rootRigidbody.transform.right, ((this.numberedBalls.Count - 1) * this.interval) + Vector3.Distance(this.rootRigidbody.position, this.firstChildRigidbody.position)), 1)); 41 42 // primaryJointを巻き取りモードに切り替えるプロパティ 43 private bool IsWinding 44 { 45 get => this.isWinding; 46 set 47 { 48 this.isWinding = value; 49 var joint = this.primaryJoint; 50 if (value) 51 { 52 joint.connectedAnchor = joint.connectedBody.transform.InverseTransformPoint(this.rootRigidbody.position); 53 joint.zMotion = ConfigurableJointMotion.Locked; 54 } 55 else 56 { 57 joint.zMotion = ConfigurableJointMotion.Free; 58 } 59 } 60 } 61 62 private void Start() 63 { 64 this.firstRigidbody = this.firstBall.GetComponent<Rigidbody>(); 65 var firstPosition = this.firstPosiBase.transform.position; //ボールの発生する位置の設定 66 this.firstBall.transform.position = firstPosition; //初期位置に初期ボールを移動させる 67 68 // 初期位置に根元のボールを設置 69 this.rootRigidbody = Instantiate(this.jyakanBall, firstPosition, Quaternion.identity).GetComponent<Rigidbody>(); 70 71 // 「firstChildRigidbody」はrootRigidbodyと直結している直接の子を表す 72 // まずはfirstBallをfirstChildRigidbodyとする 73 this.firstChildRigidbody = this.firstRigidbody; 74 75 // ヒエラルキー上での確認を容易にするため、ボールには「プレハブ名 連番」の形の名前を付ける 76 // rootRigidbodyは特別扱いで連番は付けず(さしあたり「プレハブ名 Root」とする)、firstBallを0番とする 77 this.rootRigidbody.name = $"{this.jyakanBall.name} Root"; 78 this.firstChildRigidbody.name = $"{this.jyakanBall.name} 0"; 79 80+ // firstChildRigidbodyを覚えておく 81+ this.numberedBalls.Push(this.firstChildRigidbody); 82 83 // 根元は初期位置に固定することにする 84 this.rootRigidbody.gameObject.AddComponent<FixedJoint>(); 85 86 // rootRigidbodyとfirstChildRigidbodyを接続する 87 this.connectedAnchor = Vector3.back * this.interval; 88 this.highLimit = new SoftJointLimit {limit = this.angularLimit}; 89 this.lowLimit = this.highLimit; 90 this.lowLimit.limit *= -1.0f; 91 this.drive = new JointDrive 92 { 93 maximumForce = float.MaxValue, 94 positionSpring = this.spring, 95 positionDamper = this.damper 96 }; 97 this.primaryJoint = this.rootRigidbody.gameObject.AddComponent<ConfigurableJoint>(); 98 { 99 var joint = this.primaryJoint; 100 joint.rotationDriveMode = RotationDriveMode.XYAndZ; 101 joint.angularXDrive = joint.angularYZDrive = this.drive; 102 joint.xMotion = joint.yMotion = ConfigurableJointMotion.Locked; 103 joint.zMotion = ConfigurableJointMotion.Free; 104 joint.angularXMotion = joint.angularYMotion = ConfigurableJointMotion.Limited; 105 joint.angularZMotion = ConfigurableJointMotion.Locked; 106 joint.highAngularXLimit = joint.angularYLimit = this.highLimit; 107 joint.lowAngularXLimit = this.lowLimit; 108 joint.anchor = Vector3.zero; 109 joint.autoConfigureConnectedAnchor = false; 110 joint.connectedAnchor = Vector3.zero; 111 joint.connectedBody = this.firstChildRigidbody; 112 } 113 } 114 115 // 下矢印キーを押し下げると巻き取りモード、離すと通常モードとする 116 private void Update() 117 { 118 if (Input.GetKeyDown(KeyCode.DownArrow)) 119 { 120 this.IsWinding = true; 121 } 122 123 if (Input.GetKeyUp(KeyCode.DownArrow)) 124 { 125 this.IsWinding = false; 126 } 127 } 128 129 private void FixedUpdate() 130 { 131 var rootPosition = this.rootRigidbody.position; 132 var rootRotation = this.rootRigidbody.rotation; 133 134 // キーボード操作でfirstRigidbodyを移動 135 var velocity = this.firstRigidbody.velocity; 136 velocity.z = 0.0f; 137 if (this.IsWinding) 138 { 139 // 巻き取りモードの場合、connectedAnchorを縮めることでリールの巻き取りを表現する 140 this.primaryJoint.connectedAnchor = Vector3.MoveTowards( 141 this.primaryJoint.connectedAnchor, 142 Vector3.zero, 143 this.speed * Time.deltaTime); 144 145 // secondaryJointが存在するならば、つまりfirstChildRigidbodyの次に1つ以上ボールが 146 // 存在するならば、さらにconnectedAnchorがほぼゼロまで縮んだかを調べる 147 while ((this.secondaryJoint != null) && (this.primaryJoint.connectedAnchor.sqrMagnitude < 0.01f)) 148 { 149 // connectedAnchorが縮みきっていればジョイントのつなぎ替えを行い、firstChildRigidbodyを削除する 150 var newFirstChildRigidbody = this.secondaryJoint.connectedBody; 151 var newFirstChildPosition = newFirstChildRigidbody.position; 152 var newFirstChildRotation = newFirstChildRigidbody.rotation; 153 DestroyImmediate(this.firstChildRigidbody.gameObject); 154 this.primaryJoint.connectedAnchor = newFirstChildRigidbody.transform.InverseTransformPoint(rootPosition); 155 newFirstChildRigidbody.position = rootPosition; 156 newFirstChildRigidbody.rotation = rootRotation; 157 this.primaryJoint.connectedBody = newFirstChildRigidbody; 158 newFirstChildRigidbody.position = newFirstChildPosition; 159 newFirstChildRigidbody.rotation = newFirstChildRotation; 160 161 // primaryJointの接続先を次のfirstChildRigidbodyとし、secondaryJointも更新する 162 this.firstChildRigidbody = newFirstChildRigidbody; 163 this.secondaryJoint = newFirstChildRigidbody.GetComponent<ConfigurableJoint>(); 164 165+ // 古いfirstChildRigidbodyをスタックから削除する 166+ this.numberedBalls.Pop(); 167 } 168 } 169 else 170 { 171 if (Input.GetKey(KeyCode.UpArrow)) 172 { 173 velocity.z += this.speed; 174 } 175 176 // firstChildRigidbodyに対するrootRigidbodyの相対位置をrelativePositionとする 177 var ballPosition = this.firstChildRigidbody.position; 178 var ballRotation = this.firstChildRigidbody.rotation; 179 var relativePosition = rootPosition - ballPosition; 180 181 // その相対位置ベクトルの長さがinterval以上であればボールを追加する 182 var distance = relativePosition.magnitude; 183 while (distance >= this.interval) 184 { 185 // firstChildRigidbodyからintervalだけ離れた位置に新規ボールを設置する 186 var newFirstChildPosition = ballPosition + Vector3.ClampMagnitude(relativePosition, this.interval); 187 var newFirstChildRigidbody = Instantiate(this.jyakanBall, rootPosition, rootRotation).GetComponent<Rigidbody>(); 188 var previousFirstChildName = this.firstChildRigidbody.name; 189 var previousFirstChildIndex = int.Parse(previousFirstChildName.Substring(previousFirstChildName.LastIndexOf(' ') + 1)); 190 newFirstChildRigidbody.name = $"{this.jyakanBall.name} {previousFirstChildIndex + 1}"; 191 192 // ジョイントもアタッチし、既存のジョイントをつなぎ替える 193 this.firstChildRigidbody.position = rootPosition; 194 this.firstChildRigidbody.rotation = rootRotation; 195 this.secondaryJoint = newFirstChildRigidbody.gameObject.AddComponent<ConfigurableJoint>(); 196 { 197 var joint = this.secondaryJoint; 198 joint.rotationDriveMode = RotationDriveMode.XYAndZ; 199 joint.angularXDrive = joint.angularYZDrive = this.drive; 200 joint.xMotion = joint.yMotion = joint.zMotion = ConfigurableJointMotion.Locked; 201 joint.angularXMotion = joint.angularYMotion = ConfigurableJointMotion.Limited; 202 joint.angularZMotion = ConfigurableJointMotion.Locked; 203 joint.highAngularXLimit = joint.angularYLimit = this.highLimit; 204 joint.lowAngularXLimit = this.lowLimit; 205 joint.anchor = Vector3.zero; 206 joint.autoConfigureConnectedAnchor = false; 207 joint.connectedAnchor = this.connectedAnchor; 208 joint.connectedBody = this.firstChildRigidbody; 209 } 210 this.primaryJoint.connectedBody = newFirstChildRigidbody; 211 newFirstChildRigidbody.position = newFirstChildPosition; 212 newFirstChildRigidbody.rotation = Quaternion.Lerp(ballRotation, rootRotation, this.interval / distance); 213 this.firstChildRigidbody.position = ballPosition; 214 this.firstChildRigidbody.rotation = ballRotation; 215 216 // 新しいボールを次のfirstChildRigidbodyとし、ballPositionとrelativePositionも更新する 217 this.firstChildRigidbody = newFirstChildRigidbody; 218 ballPosition = this.firstChildRigidbody.position; 219 relativePosition = rootPosition - ballPosition; 220 distance = relativePosition.magnitude; 221 222+ // 新しいfirstChildRigidbodyをスタックに追加する 223+ this.numberedBalls.Push(this.firstChildRigidbody); 224 } 225 } 226 227 this.firstRigidbody.velocity = velocity; 228 } 229}

(パート2へ続く...)

投稿2021/08/08 22:59

Bongo

総合スコア10807

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問