こちらで添付しているようなキャラがマップの端にふれると触れた部分周辺のみエフェクトが表示されるものを作りたいですがどう作っていいのか全くわかりません。
いろいろ説明が足りていない質問になってしまい申し訳ございませんがなにかアイデアをいただけませんでしょうか。
よろしくお願いいたします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
回答1件
0
ベストアンサー
方法を調べてみるにしても検索キーワードをどうしたらいいのか悩ましいですね...
ちょっと検索してみたところほとんどは無関係な記事ばかりだったのですが、POLYGONOMICON ステージ端エフェクトにステージ端のエフェクトを作ったとの日記がありました。近づくほどはっきり表示させるようなマテリアルだそうですが、おそらく比較的シンプルに作れそうでいいんじゃないかと思います。
ですがご質問者さんのご提示いただいたムービーの場合は、単純に近づくと表示されるだけでなく、触れた点を中心に波紋のようにエフェクトが広がっているようです。こういった表現をしたいとなるとひと工夫必要そうですね。
一案として、まず下記のようなスクリプトを用意し...
C#
1using System.Linq; 2using UnityEngine; 3 4public class Barrier : MonoBehaviour 5{ 6 private const int MaximumImpulseCount = 8; 7 private static readonly int ImpulsesProperty = Shader.PropertyToID("_Impulses"); 8 private static readonly Vector4[] Impulses = Enumerable.Repeat( 9 new Vector4(0.0f, 0.0f, 0.0f, float.PositiveInfinity), 10 MaximumImpulseCount).ToArray(); 11 private static int impulseIndex; 12 private static Material sharedMaterial; 13 private static bool needsUpdateMaterial; 14 15 // ここにインスペクター上でバリア用マテリアルをセットしておく 16 [SerializeField] private Material material; 17 18 public static void ApplyImpulse(Vector3 position) 19 { 20 // キャラクターがバリアにぶつかった...などで衝突エフェクトを発生させたい場合、このメソッドを実行する 21 // Impulsesにバリア衝突エフェクトの中心位置、および衝突が起こった時刻を記録するようになっている 22 // 最大でMaximumImpulseCount個のデータを記録でき、それ以上は古いデータから順に上書きされていく 23 Debug.Log($"Apply impulse {impulseIndex} at {position}"); 24 Impulses[impulseIndex].x = position.x; 25 Impulses[impulseIndex].y = position.y; 26 Impulses[impulseIndex].z = position.z; 27 Impulses[impulseIndex].w = Time.timeSinceLevelLoad; 28 impulseIndex = (impulseIndex + 1) % Impulses.Length; 29 } 30 31 private void Awake() 32 { 33 // すべてのバリアは一つのマテリアルを共有することにする 34 if ((sharedMaterial == null) && (this.material != null)) 35 { 36 sharedMaterial = this.material; 37 } 38 } 39 40 private void Start() 41 { 42 // このタイミングで個々のオブジェクトのマテリアルは共有バリアマテリアルに置き換わる 43 // そのため、非プレイモードの時点でレンダラーにセットしておくマテリアルは 44 // 何であってもかまわないということになる 45 // そこで、シーン編集の段階では常時表示される普通のマテリアルをセットしておくことで 46 // バリアがシーン上にどのように配置されているか把握しやすくなるんじゃないかと思う 47 var r = this.GetComponent<Renderer>(); 48 if (r != null) 49 { 50 r.sharedMaterial = sharedMaterial; 51 } 52 } 53 54 private void Update() 55 { 56 needsUpdateMaterial = true; 57 } 58 59 private void LateUpdate() 60 { 61 if (needsUpdateMaterial && (sharedMaterial != null)) 62 { 63 // 毎フレーム、シーン中のいずれかのBarrierが代表して 64 // 共有バリアマテリアルに現在のImpulseデータを送る 65 sharedMaterial.SetVectorArray(ImpulsesProperty, Impulses); 66 } 67 68 needsUpdateMaterial = false; 69 } 70}
それをQuadオブジェクトにアタッチ、さらにスケールを適当に広げてシーン上に配置してみました(これがマップ端の壁のつもりです)。
スクリプトにはマテリアルをセットするためのフィールドがありますが、そこには下記のようなマテリアルをセットしておきます。
ShaderLab
1Shader "Effect/Barrier" 2{ 3 Properties 4 { 5 [HDR] _Color ("Color", Color) = (8.0, 1.0, 0.0, 1.0) 6 [NoScaleOffset] _MainTex ("Pattern", 2D) = "white" {} 7 _PatternAspect ("Pattern Vertical Aspect", Float) = 1.73205080757 8 _PatternTiling ("Pattern Tiling", Float) = 2.0 9 _PatternExponent ("Pattern Exponent", Float) = 32.0 10 _ImpulseSpeed ("Impulse Speed", Range(0.01, 10.0)) = 4.0 11 _ImpulseScale ("Impulse Scale", Range(0.001, 2.0)) = 0.5 12 _ImpulseDecay ("Impulse Decay", Range(0.01, 10.0)) = 2.0 13 } 14 SubShader 15 { 16 Tags { "Queue"="Transparent" "RenderType"="Transparent" } 17 18 Pass 19 { 20 // 加算合成 21 Blend One One 22 23 // Zテストは行うがZ書き込みは行わない 24 ZTest Less 25 ZWrite Off 26 27 // 両面レンダリング 28 Cull Off 29 30 CGPROGRAM 31 #pragma vertex vert 32 #pragma fragment frag 33 34 #include "UnityCG.cginc" 35 36 // さしあたり、衝突エフェクトの同時描画数は8個までとする 37 #define MAXIMUM_IMPULSE_COUNT 8 38 39 struct appdata 40 { 41 float4 vertex : POSITION; 42 float2 uv : TEXCOORD0; 43 }; 44 45 struct v2f 46 { 47 float2 uv : TEXCOORD0; 48 float3 worldPos : TEXCOORD1; 49 float4 vertex : SV_POSITION; 50 }; 51 52 float4 _Color; 53 sampler2D _MainTex; 54 float _PatternAspect; 55 float _PatternTiling; 56 float _PatternExponent; 57 float _ImpulseSpeed; 58 float _ImpulseScale; 59 float _ImpulseDecay; 60 float4 _Impulses[MAXIMUM_IMPULSE_COUNT]; 61 62 v2f vert(appdata v) 63 { 64 v2f o; 65 o.vertex = UnityObjectToClipPos(v.vertex); 66 67 // ワールド座標を用意しておく 68 o.worldPos = mul(unity_ObjectToWorld, v.vertex); 69 70 // UVマッピングには_MainTex_STを使ったタイリング・オフセットは使用せず 71 // 自前で用意したプロパティをもとにスケーリングすることにした(ただの気まぐれ) 72 // また、バリアオブジェクトはさまざまな大きさに拡大縮小されて配置されると予想されるため 73 // XとYの拡大率をUVにかけることでパターンの大きさが一定になるようにしてみた 74 o.uv = v.uv * float2(_PatternAspect * length(unity_ObjectToWorld._11_21_31), length(unity_ObjectToWorld._12_22_32)) * _PatternTiling; 75 return o; 76 } 77 78 float getImpulseIntensity(in float4 impulse, in float3 fragPos) 79 { 80 // まずこのフラグメントと衝突中心の距離、および経過時間を求める 81 float3 p = fragPos - impulse.xyz; 82 float lsq = dot(p, p); 83 float l = sqrt(lsq); 84 float t = _Time.y - impulse.w; 85 86 // 衝撃の最前線は中心からこれだけ離れており... 87 float f = _ImpulseSpeed * t; 88 89 // 最前線がこのフラグメントを通り過ぎた距離はこうなる 90 float x = f - l; 91 92 // 値は0以上になるようにして... 93 x = max(x, 0.0); 94 95 // さらにスケーリングしてピークの太さを調節する 96 x /= _ImpulseScale; 97 98 // 衝撃の強度を求める 99 // 強度関数として原子の電子の1s軌道の動径分布関数をベースに形状を調整したものを使った 100 // 参考: http://sciencetips.web.fc2.com/quantam140.html 101 // これでなければならない理由はなく、ただグラフの形が好都合そうだったから流用しただけ 102 float i = x * x * exp(2.0 * (1.0 - x)); 103 104 // このフラグメントと衝突中心の距離の2乗に反比例して強度を減衰させる 105 i /= lsq * _ImpulseDecay; 106 107 return i; 108 } 109 110 float4 frag(v2f i) : SV_Target 111 { 112 // それぞれの衝突点に由来する衝撃強度を合算する 113 float intensity = 0.0; 114 for (int j = 0; j < MAXIMUM_IMPULSE_COUNT; j++) 115 { 116 intensity += getImpulseIntensity(_Impulses[j], i.worldPos); 117 } 118 119 // テクスチャからバリアの模様を取得して... 120 float pattern = tex2D(_MainTex, i.uv).r; 121 122 // 冪乗して見た目を調節し... 123 pattern = pow(pattern, _PatternExponent); 124 125 // 色に模様と衝撃強度を乗算して返す 126 return _Color * (pattern * intensity); 127 } 128 ENDCG 129 } 130 } 131}
マテリアルの「Pattern」プロパティにはバリアの模様となるテクスチャをセットします。
さしあたり下図のような画像を使い...
インポート設定は下図のようにしました。Wrap Modeを「Mirror」とすることで上下左右に鏡映繰り返しさせ、六角形の蜂の巣模様にしようという魂胆です。また、1チャンネルさえあれば用は足りるので、フォーマットを「R 8」にしています。
そして、とりあえずプレイヤーキャラクターには下記のスクリプトをアタッチしました。
バリアとの衝突を検知して、衝突点のワールド座標を引数にApplyImpulse
を実行するだけのものです。
C#
1using UnityEngine; 2 3[RequireComponent(typeof(Rigidbody))] 4public class BarrierImpulser : MonoBehaviour 5{ 6 [SerializeField] private string barrierTag = "Barrier"; 7 8 private void OnCollisionEnter(Collision collision) 9 { 10 if (collision.gameObject.CompareTag(this.barrierTag)) 11 { 12 Barrier.ApplyImpulse(collision.GetContact(0).point); 13 } 14 } 15}
壁を2枚、さらについでにティーポットを守る球形のバリアを1個設置して動かしてみると、下図のようになりました。わりといい雰囲気になったんじゃないでしょうかね?
投稿2020/08/21 11:55
総合スコア10811
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
退会済みユーザー
2020/08/24 01:04