ぐーるらいふ

しがないリーマンの記録。遊びのunityのメモ帳にしたい。

【Unity】uGUIでimageに穴を開けたい!(くり抜く)

ルパン三世的な…?

チュートリアルなどでゲーム画面の説明をしたい。
よくある画面を暗くして見てほしい箇所にライトを当てるような演出をしたい。

f:id:ghoul_life:20170626200151p:plain

こんなんただ塗って穴開けてるだけやん。
それだけなのに、パッと実装が分からない。
Maskじゃない、なんというかMaskの逆?
uGUIだけで出来る?どうやるんだ?

  • すでにゲームは作ってあるので、そこはなるべく触りたくない
  • uGUIベースのゲームなのでuGUIでやりたい。
  • 実装はなるべくシンプルで簡単に

どうも既存機能だけでは厳しそうな感触。
なんらかの工夫が必要か。

くりぬく?

nn-hokuson.hatenablog.com
なるほど、ColorMask 0!
これでも行けそう?だけど
裏のQueueもいじらないと駄目か…。うーむ。
あ、そうか別Layerにして、サブカメラにすればいけそうかな?

ワイプ、トランジション

裏に影響なく画面全体を被せるのはこれが近いのかな?
tsubakit1.hateblo.jp
これを応用すればやりたいことが出来そう?

おお!これか!?これじゃん!
nn-hokuson.hatenablog.com
これを応用してみよう。

実装してみる

もうほとんどそのままだけどパラメータを外から設定出来るように。

Shader "Custom/HoleViewShader"
{
	Properties{
		_BackColor("background color", Color) = (0, 0, 0, 0) // 背景色
		_HoleX("Hole Position X", float) = 0 // 穴の位置X
		_HoleY("Hole Position Y", float) = 0 // 穴の位置Y
		_Radius("hole radius", float) = 0 // 穴の大きさ
		_ScreenW("Screen Width" , float) = 960.0 // 画面の幅
		_ScreenH("Screen Height" , float) = 640.0 // 画面の高さ
		}

	SubShader{
			Tags{ "Queue" = "Transparent" } // alphaに対応するのに必要

			Blend SrcAlpha OneMinusSrcAlpha // alphaに対応するために必要
			Pass{
				CGPROGRAM

				#include "UnityCG.cginc"
				#pragma vertex vert_img
				#pragma fragment frag

				// Propertiesの値をShaderに渡す
				float _HoleX;
				float _HoleY;
				fixed4 _BackColor;
				float _Radius;
				float _ScreenW;
				float _ScreenH;

				fixed4 frag(v2f_img i) : COLOR{

					i.uv.x *= _ScreenW / _ScreenH; // アスペクト比を計算してあげる

					if (distance(i.uv, fixed2(_HoleX, _HoleY)) < _Radius){
						discard; // 指定位置より一定距離以内だったら処理を飛ばすだけ
					}
					return _BackColor;
				}
				ENDCG
			}
		}
}

値を渡すスクリプト側はこんな感じ。

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class HoleView : MonoBehaviour {

    void Start()
    {
        // 外部から設定する
        SetHole(new Vector2(480, 320), 0.2f);
    }

    public void SetHole(Vector2 pos, float radius)
    {

        // 画面との比率を出す
        var pinPos = new Vector2(pos.x / Screen.width, pos.y / Screen.height);

        // アスペクト比の問題なのか、位置的にはこのあたりで調整すると思った位置になるので計算してあげる
        // x range 0.45 - 1.05 この値が画面端から画面端でいい感じの所
        // y range 0.3 - 0.7

        // 比率と範囲値を計算して値を出す
        pinPos.x *= (1.05f - 0.45f);
        pinPos.y *= (0.7f - 0.3f);

        // 最下値を足して範囲に入れる
        pinPos.x += 0.45f;
        pinPos.y += 0.3f;

        // shaderに値を渡す
        var img = GetComponent<Image>();
        img.material.SetFloat("_HoleX", pinPos.x);
        img.material.SetFloat("_HoleY", pinPos.y);
        img.material.SetFloat("_Radius", radius);
        img.material.SetFloat("_ScreenW", Screen.width);
        img.material.SetFloat("_ScreenH", Screen.height);
    }
}

githubにもさくっと。
github.com



Inspectorはこれだけ!
Image一個あればOK!

f:id:ghoul_life:20170626190258p:plain

これで超簡単!後からちょい載せで使えるヤーツの完成です。
uGUI同士ならCanvasのSortOrderとかで順番をいい感じにしてあげてください。

注意事項として

  • ホールの形

え、星型にくり抜きたい?shaderで書くんだ!ってのは嘘で、
複雑な形にくり抜きたいならLayer&サブカメラ&ColorMask0でやったほうがいいかも。

こればっかりはどうしようも無い?
汎用的に外部パラメータ化してスクリプトから渡すように。
後は実際にゲーム画面を見ながらスクリプト側で調整してあげて下さい。