[UNITY]ScrollRectを拡張してページめくりを実装する。

UNITY

えんちゃです。ページごとにきちっと止まるように、ScrollRectを拡張してページスクロール機能を作っていきます。

基本機能にないよね?よね?もしあったらご教授願います。m(_ _)m

[実装内容]
Unity 2019.4.1
・スクロールすると次のページに移動する。
・スクロール量が少ないと移動しない。

出来上がりはこんな感じです。

構成

Scene
 └─Canvas
  └─Scroll View
   └─Viewport
    └─Content
     ├─Page1
     ├─Page2
     ├─ ….

構成はこんな感じになっています。
ヒエラルキーを右クリックし、UI>Scroll View を押下すればContentまで勝手に上の構成になります。

Content以下にページの内容を設定していきます。
ViewPortのMaskのチェックを外すと画面表示外もシーンに表示されるので見やすいと思います。横一列になるようページを作成していきます。

また、Contentのサイズはページ1枚の幅×ページ数できっちり合わせるようにします。

これで構成は終わりです。Maskのチェックを戻すことを忘れないようにしましょう。

ページスクロールスクリプトの作成

ページスクロールには、今何ページか、何%スクロールしたか、の2つを知る必要があります。
それを知るためには、ScrollRect.horizontalNormalizedPositionを使用します。

上記URLの説明の通り、水平方向のスクロール位置を 0 から 1 の間で返してくれます。

調べると、ページごとの値は

という値になります。

これにより、ページ数とhorizontalNormalizedPositionの関係を求めていきましょう。

画像がPage1から始まっていて、ちょっとわかりにくいのですが、
0ページから始まるとして、ページとhorizontalNormalizedPositionの関係は
ページ = horizontalNormalizedPosition * (総ページ数 – 1)
で表すことができます。

つぎにスクロール量です。
今回の実装内容である、「スクロール量が少ないと移動しない」を実装するため、スクロール量が20%以下なら移動させない。20%以上なら次ページに移動させる機能を作っていきます。
どのように求められるか考えていきましょう。


例えば先頭のページをスライドさせて、0.1の所で離したとします。
ページ1枚の幅は0.25 = 1/4 なので
0.1 / (1/4) = 0.1 * 4 = 0.4  となり、40%移動したことがわかります。
これを一般化すると
(離したときのhorizontalNormalizedPosition ― タップ時のhorizontalNormalizedPosition) × (総ページ数 ― 1) = スクロール量
でスクロール量が求められます。
またスクロール量がマイナスの場合は逆方向にスライドしたということもわかります。

プログラム実装

今回ScrollRectを継承してプログラムを書いていきます。
タップ時と離した時のhorizontalNormalizedPosition を求めるために、
OnBeginDrag と OnEndDrag をオーバライドして、書き込んでいきます。

using UnityEngine;
using UnityEngine.UI;
using UnityEngine.EventSystems;

public class ScrollPages : ScrollRect
{
    //ページ数
    public int PageNumber;
    //しきい値
    public float ThresholdValue;

    private float beforePosition;

    public override void OnBeginDrag(PointerEventData data)
    {
        base.OnBeginDrag(data);
        //座標取得
        beforePosition = horizontalNormalizedPosition;
    }

    public override void OnEndDrag(PointerEventData data)
    {
        base.OnEndDrag(data);
        //次のページ取得
        float nextPage = CalcNextPage(beforePosition, horizontalNormalizedPosition);
        //座標設定
        horizontalNormalizedPosition = nextPage / (PageNumber - 1);
    }

    /// 
    /// スライド量がしきい値に満たない場合は自ページ
    /// 右にスライドしたときは次ページ
    /// 左にスライドしたときは前ページを返す
    /// 
    /// クリック時座標
    /// 離したときの座標
    /// 
    private float CalcNextPage(float beforPosition, float afterPosition)
    {
        //移動量(%)を求める
        float differencePosition = (afterPosition - beforPosition) * (PageNumber - 1);
        
        //移動量(%)がしきい値以下の場合、自ページを返す
        if (Mathf.Abs(differencePosition) <= ThresholdValue)
        {
            return beforPosition * (PageNumber - 1);
        }

        if (differencePosition > 0)
        {
            //右にスライドした時は切り上げ(次ページ)
            return Mathf.Ceil(afterPosition * (PageNumber - 1));
        }
        else
        {
            //左にスライドした時は切り捨て(前ページ)
            return Mathf.Floor(afterPosition * (PageNumber - 1));
        }
    }
}

これにてプログラム実装は完了です。

エディタ拡張

ScrollViewのインスペクタからScrollRectを消して新しく作ったプログラムScrollPagesに置き換えてみましょう。

むむむっ、、しきい値とページ数をインスペクタから設定できません。
これを解決するためにエディタの拡張をしていきます。

そのためにScrollRectEditorを継承して、ScrollPagesEditor を作っていきます。

using UnityEditor;
using UnityEditor.UI;

[CustomEditor(typeof(ScrollPages))]
public class ScrollPagesEditor : ScrollRectEditor
{
    public override void OnInspectorGUI()
    {
        // 内部キャッシュから値をロードする
        serializedObject.Update();

        // 元々のインスペクタ内容を記述.
        base.OnInspectorGUI();

        // プロパティを取得する
        var pageNumber = serializedObject.FindProperty("PageNumber");
        var thresholdValue = serializedObject.FindProperty("ThresholdValue");


        // プロパティをインスペクタから編集できるように設定
        EditorGUILayout.PropertyField(pageNumber);
        EditorGUILayout.PropertyField(thresholdValue);

        // 内部キャッシュに値を保存する
        serializedObject.ApplyModifiedProperties();
    }
}

これをAssets/Editor 配下に置きましょう。
コンパイルの関係でEditorという特殊フォルダに入れないとうまく動かないみたいです。

これで、設定できるようになりました。
ページ数と何パーセントのスクロール量で次ページに移るか設定して完成です!!!

コード自体は少ないけど初めてエディタ拡張したため、すごい時間かかりました。

コメント

タイトルとURLをコピーしました