はじめに
こんにちは、NEGIです。
新歓ブログリレーも早いもので24日目ですね。見てくださっている方、いつもありがとうございます。
さて今回は、以前Unityでレーダーのようなものを作ったのでその話をしようと思います。
作ったもの
最終的にできたものはこのゲームです。(PCからでは実際に遊べます)
{{< iframe "https://welshonion.github.io/WebGL_Flak/" 720 1000>}}
Webからだと読み込みがちょっとだいぶ重いのですが、船の上から敵機を打ち落とすという内容のゲームなので興味があったら遊んでみてください。
PC版はこちらからダウンロードできます。
そして、このゲームの右上に表示されているものが今回紹介する自作レーダーです。
もともと上述のゲームに使用したかったのもあり、仕様は船舶用レーダーに酷似しています。
こんな感じで緑の線がぐるぐる回って、敵とか魚の位置が分かるやつです。どっかでみたことありますか?
大戦中の詳しいレーダー(探信儀)の話なんかはこちらの方の説明がとても丁寧でわかりやすいのでよかったら見てください。当時の海軍の対空用電探はPPIじゃないとか言わないでください。泣きます。
少し脱線しましたが、このレーダーを、どのようにUnity上で作ったのかを書いていきます。
仕組み1~位置の取得~
いきなりUnity臭い画面で恐縮ですが、簡単のため解説にはこちらのステージを使います。
後ろにあるグレーの箱の黄色い部分からレーダーが出てると考えてください。 ちなみに真ん中にいるのはランダムの部員兼公式キャラクターのらんだむちゃんです。かわいいですね。
さて、このレーダーは回転し、1度ごとに当たった物質までの距離を教えてくれます。つまりRayCastですね。 そして360個のfloat配列の、今のレーダーの角度と対応する部分を更新することで全方向の距離を順に取得していきます。 これで今のレーダーの向きと近くの物体情報が取得できたのでSetFloatArrayでシェーダー(Unlitです)に送ってあげます。
仕組み2~描画~
今度は送られた位置情報から実際にこのレーダーの画面を描画します。
ぐるぐる回る棒部分や円の描画はこちらを参考にしました。簡単に言うと、半径rの部分だけ緑色とかを指定して、それをどんどん重ねていくわけです。
この棒の角度とレーダーの角度を一致させ、360°方向の物体情報を合わせて描画するように改良したコードが以下のものです。
RaderShader.shader
Shader "Unlit/RadarShader"
{
Properties
{
_MainTex ("Texture", 2D) = "white" {}
_FValue("Rot_Value",float) = 0
//_FDIST("Dist_Value",float[3]) = {1,2,1}
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
UNITY_FOG_COORDS(1)
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
uniform float _FValue;
uniform float _FDIST[360];
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
UNITY_TRANSFER_FOG(o,o.vertex);
return o;
}
#define PI 3.14159265359
float main_range(float2 st) {
float r = distance(st, float2(0.5, 0.5));
return step(r, 0.5);
}
float4 disc_guide(float2 st,float radius, float width) {
fixed r = distance(st,fixed2(0.5,0.5));
return step(radius - width, r)*step(1 - radius,1-r);
}
float disc(float2 st) {
return disc_guide(st, 0.1, 0.01) + disc_guide(st, 0.3, 0.01) + disc_guide(st, 0.5, 0.01);
//50 0.4
}
float grad_rot(float2 st,float width) {
float dx = 0.5 - st.x;
float dy = 0.5 - st.y;
float rad = atan2(dx, dy);
rad = rad * 180 / PI;
rad = rad + 180;
float offset = (_FValue ) % 360;
float offset2 = (_FValue + 60) % 360;
float d1 = distance(rad, offset) / width;
float d2 = distance(rad, offset+360) / width;
float d3 = distance(rad, offset-360) / width;
return 1-min(min(d1, d2), d3);
}
float main_radar(float2 st) {
float dx = 0.5 - st.x;
float dy = 0.5 - st.y;
float rad = atan2(dx, dy);
rad = rad * 180 / PI;
rad = rad + 180;
float offset = (_FValue ) % 360;
float offset2 = (_FValue ) % 360 + 360;
float s1 = step(rad, offset);
float s2 = step(offset-60, rad);
float s3 = step(rad, offset2);
float s4 = step(offset2-60, rad);
return (s1 * s2 + s3 * s4) *grad_rot(st,60)+disc(st);
}
float main_position(float2 st) {
float dx = 0.5 - st.x;
float dy = 0.5 - st.y;
float rad = atan2(dx, dy);
rad = (rad * 180 / PI ) +179;
float r = distance(st, fixed2(0.5, 0.5));
float dist = _FDIST[rad];
return (step(dist + 2.0f,r) * step(r, dist + 2.05f));
}
fixed4 frag(v2f i) : SV_Target
{
float2 st = i.uv;
fixed4 black = fixed4(0,0,0,0);
fixed4 write = fixed4(1, 1, 1, 1);
fixed4 green = fixed4(0, 1, 0, 0);
return lerp(black, green, (main_position(st) + main_radar(st)) * main_range(st));
}
ENDCG
}
}
}
数値はきれいに描画できるように調整してあったり、調整用に冗長的だったりします。main_radarでレーダー部分、main_positionで周辺の情報を描画しfragにて合成しています。また_FValueは角度、_FDIST[360]が各角度の距離情報です。詳しい説明は控えますが、興味ある方は解読してみてください。
これで周囲の物体をすべて認識できていますね。よく見るとらんだむちゃんの移動も正しく更新されています。
良い点
- 一応ちゃんと動く
- 情報更新のされ方がレーダーの回転と一致している
- 画像のようにどこにでも貼れる(コックピットとかも作れる?)
課題
- 重い
- RayCastがそもそも重い
- 毎フレーム更新しているのも原因かもしれない
- 平面的にしか位置情報を取得できない
- 平面だけの取得で重いので空間全部取ろうとすると・・・
- 最初に当たったものしか表示されない。
- つまり壁の向こうのものは表示されません
- これも仕様上仕方ない部分がある(言い訳)
まとめ
みんなもUnityのシェーダーで遊んでみよう!
配布
ここからアセットとしてダウンロードできます。動作保証はできかねますがもしよかったら遊んでみてください。