しっぽを追いかけて

ぐるぐるしながら考えています

Unity と猫の話題が中心   掲載内容は個人の私見であり、所属組織の見解ではありません

Unity で UI の挿入先にヒントエフェクトを表示する

※ これは 2020/11/27 時点の Unity 2020.1.15f1 の情報です

最新版では動作が異なる可能性がありますのでご注意ください

前回の UI のプレースホルダーの続き

プレースホルダーとしてすき間ができるようにはなったが、もう少しわかりやすくしたいので、このすき間を明滅させたい

明滅プレースホルダ

ここに入りますよ~みたいな感じに

まずは明滅する空間をプレハブで作る

名前は Placeholder にしてこんな感じに設定

Placeholder プレハブ

明滅をさせたいので blink という名前のアニメーションを作って張り付けている

このアニメーションの内容はこんな感じ

明滅アニメーション

CanvasGroupAlpha を 1.0 ~ 0.1 の間で反復変化させただけ

CanvasGroupAlpha は直下の Image だけでなく、子要素も含めた全体の透過率を変化できるので便利

さらにこのアニメーションはいくつか設定を変更

Wrap ModeLoop でループ再生するようにして Debug 表示に切り替えたあと・・・

Loop と Debug 表示切替

Legacy にチェックを入れる

Legacy 指定

これは Animation Controller を経由せずに Animation コンポーネントに直接この Animation Clip を指定する場合に必要

あとは Draggable.cs を次のように修正

using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.EventSystems;

[RequireComponent(typeof(CanvasGroup))]
public class Draggable : MonoBehaviour
{
    [SerializeField]
    private GameObject prefab = null;

    private Transform root;
    private Transform area;
    private Transform self;
    private CanvasGroup canvasGroup = null;
    private GameObject placeholder = null;

    public void Awake()
    {
        this.self = this.transform;
        this.area = this.self.parent;
        this.root = this.area.parent;
        this.canvasGroup = this.GetComponent<CanvasGroup>();
    }

    public void OnBeginDrag(BaseEventData eventData)
    {
        // プレースホルダー生成
        this.placeholder = GameObject.Instantiate(this.prefab, this.area);
        // Instanciate による初期座標を補正
        this.placeholder.transform.localPosition = Vector3.zero;

        // 元の場所にプレースホルダーを残す
        this.placeholder.transform.SetSiblingIndex(this.self.GetSiblingIndex());

        // ドラッグできるよういったん DropArea の上位に移動する
        this.self.SetParent(this.root);

        // UI 機能を一時的無効化
        this.canvasGroup.blocksRaycasts = false;
    }

    public void OnDrag(BaseEventData eventData)
    {
        this.self.localPosition = GetLocalPosition(((PointerEventData)eventData).position, this.transform);

        AddToArea(this.placeholder.transform, this.self.position.x, (PointerEventData)eventData);
    }

    private static Vector3 GetLocalPosition(Vector3 position, Transform transform)
    {
        // 画面上の座標 (Screen Point) を RectTransform 上のローカル座標に変換
        RectTransformUtility.ScreenPointToLocalPointInRectangle(
            transform.parent.GetComponent<RectTransform>(),
            position,
            Camera.main,
            out var result);
        return new Vector3(result.x, result.y, 0);
    }

    private static void AddToArea(Transform item, float x, PointerEventData eventData)
    {
        // 座標位置に DropArea があったら挿入対象にする
        var dropArea = GetRaycastArea(eventData);
        if (dropArea == null)
        {
            return;
        }

        // 挿入位置を x 座標から計算
        var area = dropArea.transform;
        var index = area.childCount;
        for (var i = 0; i < area.childCount; i++)
        {
            if (x < area.GetChild(i).position.x)
            {
                index = i;
                if (item.GetSiblingIndex() < index)
                    index--;

                break;
            }
        }

        item.SetParent(area);
        item.SetSiblingIndex(index);
    }

    public void OnEndDrag(BaseEventData eventData)
    {
        // プレースホルダーを破棄
        Destroy(this.placeholder);

        // DropArea 移動
        AddToArea(this.self, this.self.position.x, (PointerEventData)eventData);
        this.area = this.self.parent;

        // UI 機能を復元
        this.canvasGroup.blocksRaycasts = true;
    }

    /// <summary>
    /// イベント発生地点の DropArea を取得する
    /// </summary>
    /// <param name="eventData">イベントデータ</param>
    /// <returns>DropArea</returns>
    private static DropArea GetRaycastArea(PointerEventData eventData)
    {
        var results = new List<RaycastResult>();
        EventSystem.current.RaycastAll(eventData, results);

        return results.Select(x => x.gameObject.GetComponent<DropArea>())
            .FirstOrDefault(x => x != null);
    }
}

SerializeField 経由で Placeholder のプレハブを渡せるようにして、これまでコードで生成していたプレースホルダーを GameObject.Instantiate() を利用して生成するようにしただけ

最後に Placeholder のプレハブを Draggable.cs を張り付けた Item らに設定する

Placeholder プレハブを設定

さてお試し

明滅プレースホルダ

できたできた!