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

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

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

Q&A

解決済

1回答

770閲覧

マップ端のエフェクトについて

退会済みユーザー

退会済みユーザー

総合スコア0

0グッド

0クリップ

投稿2020/08/20 09:27

こちらで添付しているようなキャラがマップの端にふれると触れた部分周辺のみエフェクトが表示されるものを作りたいですがどう作っていいのか全くわかりません。
いろいろ説明が足りていない質問になってしまい申し訳ございませんがなにかアイデアをいただけませんでしょうか。
よろしくお願いいたします。

https://youtu.be/6yTbVFXOW60?t=214

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

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

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

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

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

guest

回答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」プロパティにはバリアの模様となるテクスチャをセットします。
さしあたり下図のような画像を使い...

図1

インポート設定は下図のようにしました。Wrap Modeを「Mirror」とすることで上下左右に鏡映繰り返しさせ、六角形の蜂の巣模様にしようという魂胆です。また、1チャンネルさえあれば用は足りるので、フォーマットを「R 8」にしています。

図2

そして、とりあえずプレイヤーキャラクターには下記のスクリプトをアタッチしました。
バリアとの衝突を検知して、衝突点のワールド座標を引数に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個設置して動かしてみると、下図のようになりました。わりといい雰囲気になったんじゃないでしょうかね?

図3

投稿2020/08/21 11:55

Bongo

総合スコア10811

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

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

退会済みユーザー

退会済みユーザー

2020/08/24 01:04

Bongoさんありがとうございます…!参考スクリプトだけでなく素敵なサンプルまであり本当に驚きました。 まさにこんな感じですので参考にさせていただきます。本当にありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.35%

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

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

質問する

関連した質問