Bladeren bron

BowArrow Part02

slambb 2 jaren geleden
bovenliggende
commit
12ad11577c
100 gewijzigde bestanden met toevoegingen van 4503 en 0 verwijderingen
  1. 8 0
      Assets/BowArrow/DebugShoot.meta
  2. 63 0
      Assets/BowArrow/DebugShoot/BluetoothClient.cs
  3. 11 0
      Assets/BowArrow/DebugShoot/BluetoothClient.cs.meta
  4. 45 0
      Assets/BowArrow/DebugShoot/BluetoothDispatcher.cs
  5. 11 0
      Assets/BowArrow/DebugShoot/BluetoothDispatcher.cs.meta
  6. 26 0
      Assets/BowArrow/DebugShoot/DebugConnectArrow.cs
  7. 11 0
      Assets/BowArrow/DebugShoot/DebugConnectArrow.cs.meta
  8. 58 0
      Assets/BowArrow/DebugShoot/DebugLine.cs
  9. 11 0
      Assets/BowArrow/DebugShoot/DebugLine.cs.meta
  10. 8 0
      Assets/BowArrow/FancyScrollView.meta
  11. 8 0
      Assets/BowArrow/FancyScrollView/FocusOn.meta
  12. 79 0
      Assets/BowArrow/FancyScrollView/FocusOn/Cell.cs
  13. 12 0
      Assets/BowArrow/FancyScrollView/FocusOn/Cell.cs.meta
  14. 16 0
      Assets/BowArrow/FancyScrollView/FocusOn/Context.cs
  15. 12 0
      Assets/BowArrow/FancyScrollView/FocusOn/Context.cs.meta
  16. 34 0
      Assets/BowArrow/FancyScrollView/FocusOn/ItemData.cs
  17. 12 0
      Assets/BowArrow/FancyScrollView/FocusOn/ItemData.cs.meta
  18. 101 0
      Assets/BowArrow/FancyScrollView/FocusOn/ScrollView.cs
  19. 12 0
      Assets/BowArrow/FancyScrollView/FocusOn/ScrollView.cs.meta
  20. 8 0
      Assets/BowArrow/FancyScrollView/Sources.meta
  21. 8 0
      Assets/BowArrow/FancyScrollView/Sources/Editor.meta
  22. 124 0
      Assets/BowArrow/FancyScrollView/Sources/Editor/ScrollerEditor.cs
  23. 13 0
      Assets/BowArrow/FancyScrollView/Sources/Editor/ScrollerEditor.cs.meta
  24. 8 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime.meta
  25. 8 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Core.meta
  26. 76 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyCell.cs
  27. 12 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyCell.cs.meta
  28. 220 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyScrollView.cs
  29. 12 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyScrollView.cs.meta
  30. 8 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView.meta
  31. 76 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyCellGroup.cs
  32. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyCellGroup.cs.meta
  33. 185 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridView.cs
  34. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridView.cs.meta
  35. 47 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewCell.cs
  36. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewCell.cs.meta
  37. 24 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewContext.cs
  38. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewContext.cs.meta
  39. 20 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyCellGroupContext.cs
  40. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyCellGroupContext.cs.meta
  41. 19 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyGridViewContext.cs
  42. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyGridViewContext.cs.meta
  43. 8 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect.meta
  44. 305 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRect.cs
  45. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRect.cs.meta
  46. 61 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectCell.cs
  47. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectCell.cs.meta
  48. 19 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectContext.cs
  49. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectContext.cs.meta
  50. 19 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/IFancyScrollRectContext.cs
  51. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/IFancyScrollRectContext.cs.meta
  52. 8 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller.meta
  53. 196 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/EasingCore.cs
  54. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/EasingCore.cs.meta
  55. 16 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementDirection.cs
  56. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementDirection.cs.meta
  57. 17 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementType.cs
  58. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementType.cs.meta
  59. 14 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/ScrollDirection.cs
  60. 11 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/ScrollDirection.cs.meta
  61. 602 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/Scroller.cs
  62. 12 0
      Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/Scroller.cs.meta
  63. 8 0
      Assets/BowArrow/Fonts.meta
  64. 146 0
      Assets/BowArrow/Fonts/CustomFontImportor.cs
  65. 11 0
      Assets/BowArrow/Fonts/CustomFontImportor.cs.meta
  66. BIN
      Assets/BowArrow/Fonts/HarmonyOS_Sans_SC_Regular.ttf
  67. 21 0
      Assets/BowArrow/Fonts/HarmonyOS_Sans_SC_Regular.ttf.meta
  68. 8 0
      Assets/BowArrow/Fonts/MySDF.meta
  69. 1095 0
      Assets/BowArrow/Fonts/MySDF/Billboard.asset
  70. 8 0
      Assets/BowArrow/Fonts/MySDF/Billboard.asset.meta
  71. 8 0
      Assets/BowArrow/Fonts/MySDF/TTC.meta
  72. BIN
      Assets/BowArrow/Fonts/MySDF/TTC/msyh.ttc
  73. 21 0
      Assets/BowArrow/Fonts/MySDF/TTC/msyh.ttc.meta
  74. BIN
      Assets/BowArrow/Fonts/智能黑体.ttf
  75. 21 0
      Assets/BowArrow/Fonts/智能黑体.ttf.meta
  76. 8 0
      Assets/BowArrow/Materials.meta
  77. 8 0
      Assets/BowArrow/Materials/Color.meta
  78. 79 0
      Assets/BowArrow/Materials/Color/ColorGreen.mat
  79. 8 0
      Assets/BowArrow/Materials/Color/ColorGreen.mat.meta
  80. 8 0
      Assets/BowArrow/Models.meta
  81. 8 0
      Assets/BowArrow/Modules.meta
  82. 8 0
      Assets/BowArrow/Resources.meta
  83. 8 0
      Assets/BowArrow/Resources/Audios.meta
  84. 8 0
      Assets/BowArrow/Resources/Audios/Animal.meta
  85. BIN
      Assets/BowArrow/Resources/Audios/Animal/arrow_enter.mp3
  86. 22 0
      Assets/BowArrow/Resources/Audios/Animal/arrow_enter.mp3.meta
  87. BIN
      Assets/BowArrow/Resources/Audios/Animal/bird_injured.mp3
  88. 22 0
      Assets/BowArrow/Resources/Audios/Animal/bird_injured.mp3.meta
  89. BIN
      Assets/BowArrow/Resources/Audios/Animal/man_injured.wav
  90. 22 0
      Assets/BowArrow/Resources/Audios/Animal/man_injured.wav.meta
  91. BIN
      Assets/BowArrow/Resources/Audios/Animal/rabbit_injured.mp3
  92. 22 0
      Assets/BowArrow/Resources/Audios/Animal/rabbit_injured.mp3.meta
  93. BIN
      Assets/BowArrow/Resources/Audios/Animal/wolf_die.wav
  94. 22 0
      Assets/BowArrow/Resources/Audios/Animal/wolf_die.wav.meta
  95. BIN
      Assets/BowArrow/Resources/Audios/Animal/wolf_injured.mp3
  96. 22 0
      Assets/BowArrow/Resources/Audios/Animal/wolf_injured.mp3.meta
  97. BIN
      Assets/BowArrow/Resources/Audios/btn.wav
  98. 22 0
      Assets/BowArrow/Resources/Audios/btn.wav.meta
  99. BIN
      Assets/BowArrow/Resources/Audios/hit.mp3
  100. 22 0
      Assets/BowArrow/Resources/Audios/hit.mp3.meta

+ 8 - 0
Assets/BowArrow/DebugShoot.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 281469258256bcb449a8f114c82be029
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 63 - 0
Assets/BowArrow/DebugShoot/BluetoothClient.cs

@@ -0,0 +1,63 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+using JCEngineCore;
+
+[Serializable]
+class JCData {
+    public string uuid;
+    public int type; 
+    public string func;
+    public string[] args;
+}
+
+public class BluetoothClient : MonoBehaviour
+{
+    [SerializeField] string serverIP = "110.43.54.43";
+    [SerializeField] Text text;
+    BleDebugClient client = new BleDebugClient(); 
+    public static System.Action<byte, byte[]> onDataReceived;
+    public static BluetoothClient ins;
+
+    void Start()
+    {
+        ins = this;
+        JCEngine.bootNew("ws://" + serverIP + ":9888/BLE", client);
+    }
+
+    public static void UploadData(byte sign, byte[] bytes) 
+    {
+        if (ins && ins.client.isValid)
+        {
+            string data = String.Join(",", bytes);
+            ins.client.call("uploadData", sign.ToString(), data);
+            ins.Log("正在上传数据\n" + data);
+        }
+    }
+
+    public static void UploadError(params string[] errors) {
+        if (ins) {
+            ins.client.call("showError", string.Join("\n", errors));
+        }
+    }
+
+    public void Log(string text) {
+        if (this.text != null)
+        {
+            this.text.text = text;
+        }
+    }
+}
+class BleDebugClient : JCEntity {
+
+    public void receiveData(string sign, string byteStrList) {
+        string[] byteStrs = byteStrList.Split(',');
+        byte[] bytes = new byte[byteStrs.Length];
+        for (int i = 0; i < bytes.Length; i++)
+        {
+            bytes[i] = Byte.Parse(byteStrs[i]);
+        }
+        BluetoothClient.ins.Log("接收数据\n" + byteStrList);
+        BluetoothClient.onDataReceived?.Invoke(byte.Parse(sign), bytes);
+    }
+}

+ 11 - 0
Assets/BowArrow/DebugShoot/BluetoothClient.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e5464d6e6018e9446afd6b54a9ef1431
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 45 - 0
Assets/BowArrow/DebugShoot/BluetoothDispatcher.cs

@@ -0,0 +1,45 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+
+public class BluetoothDispatcher : MonoBehaviour
+{
+    public static System.Action<byte[]> aim;
+    public static System.Action<byte[]> shoot;
+
+    void Start()
+    {
+        BluetoothClient.onDataReceived = Dispatch;
+    }
+
+    void Dispatch(byte sign, byte[] data)
+    {
+        if (!IsWindows()) return;
+        string logStr = sign + ", LEN " + data.Length;
+        logStr +=  ", Bytes " + String.Join(",", data);
+        Debug.Log(logStr);
+        if (sign == 0 && aim != null)
+        {
+            aim(data);
+        } 
+        else if (sign == 1 && shoot != null)
+        {
+            shoot(data);
+        }
+    }
+
+    int platformID = -1;
+
+    void SetPlatformID() 
+    {
+        if (Application.platform == RuntimePlatform.WindowsEditor) platformID = 1;
+        else platformID = 2;
+    }
+
+    bool IsWindows() 
+    {
+        if (platformID == -1) SetPlatformID();
+        return platformID == 1;
+    }
+}

+ 11 - 0
Assets/BowArrow/DebugShoot/BluetoothDispatcher.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0640518c4d8627a41a16e391f6b84960
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 26 - 0
Assets/BowArrow/DebugShoot/DebugConnectArrow.cs

@@ -0,0 +1,26 @@
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+
+public class DebugConnectArrow : MonoBehaviour
+{
+    [SerializeField] GameObject btnConnectArrow;
+
+    void FixedUpdate()
+    {
+        UpdateBtnForConnect();
+    }
+
+    BluetoothStatusEnum bowStatus;
+    BluetoothStatusEnum arrowStatus;
+
+    void UpdateBtnForConnect() {
+        if (BluetoothShoot.ins && arrowStatus != BluetoothShoot.ins.status) {
+            arrowStatus = BluetoothShoot.ins.status;
+            (int textID, Color color) = BluetoothStatus.GetStatusInfo(BluetoothShoot.ins.status);
+            btnConnectArrow.GetComponentInChildren<TextAutoLanguage>().SetText(textID);
+            btnConnectArrow.GetComponentInChildren<Text>().color = color;
+        }
+    }
+}

+ 11 - 0
Assets/BowArrow/DebugShoot/DebugConnectArrow.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: c36b5b19aefecd649a4cebb57e7af313
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 58 - 0
Assets/BowArrow/DebugShoot/DebugLine.cs

@@ -0,0 +1,58 @@
+using UnityEngine;
+
+public class DebugLine : MonoBehaviour
+{
+    LineRenderer lr;
+    Vector3[] vs = new Vector3[100];
+    Vector3[] vs1 = new Vector3[303];
+    float startX = -6;
+    float len = 12;
+
+    static DebugLine ins;
+
+    void Start()
+    {
+        ins = this;
+
+        this.lr = this.GetComponent<LineRenderer>();
+        for (int i = 0; i < ins.vs.Length; i++) 
+        {
+            this.vs[i].x = startX + len * i / ins.vs.Length; 
+        }
+
+        LineRenderer lr1 = this.transform.GetChild(0).GetComponent<LineRenderer>();
+        for (int i = 0; i < ins.vs1.Length; i+=3) 
+        {
+            this.vs1[i].x = this.vs1[i+1].x = this.vs1[i+2].x = startX + len * i / 3 / this.vs.Length; 
+            this.vs1[i+1].y = -0.3f; 
+        }
+        lr1.SetPositions(this.vs1);
+    }
+
+    void OnDestroy()
+    {
+        if (ins == this) ins = null;
+    }
+
+    public static void show(float value) 
+    {
+        if (ins != null) {
+            for (int i = ins.vs.Length - 1; i > 0; i--)
+            {
+                ins.vs[i].y = ins.vs[i - 1].y;
+            }
+            ins.vs[0].y = value;
+            ins.lr.SetPositions(ins.vs);
+        }
+    }
+
+    public static void showSteady(float value)
+    {
+        if (ins != null) {
+            Transform t = ins.transform.Find("SteadyLine");
+            Vector3 p = t.localPosition;
+            p.y = value;
+            t.localPosition = p;
+        }
+    }
+}

+ 11 - 0
Assets/BowArrow/DebugShoot/DebugLine.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 0567b20da927d5a408a08a3cab217538
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 58646a2f44332d7448b797d51e40dfab
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/FocusOn.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9231c3e9ee2ea2f499765ea1083a3a53
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 79 - 0
Assets/BowArrow/FancyScrollView/FocusOn/Cell.cs

@@ -0,0 +1,79 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+
+namespace FancyScrollView.FocusOn
+{
+    class Cell : FancyCell<ItemData, Context>
+    {
+        [SerializeField] Animator animator = default;
+        [SerializeField] Text message = default;
+        [SerializeField] Image image = default;
+        [SerializeField] Button button = default;
+        [SerializeField] Image icon = default;
+        [SerializeField] Text title = default;
+        [SerializeField] GameObject line = default;
+
+        
+
+        static class AnimatorHash
+        {
+            public static readonly int Scroll = Animator.StringToHash("scroll");
+        }
+
+        public override void Initialize()
+        {
+            button.onClick.AddListener(() => Context.OnCellClicked?.Invoke(Index,true));
+        }
+
+        public void onCellClickedEvent() {
+            Context.OnCellClicked?.Invoke(Index, false);
+        }
+
+        public override void UpdateContent(ItemData itemData)
+        {
+            message.text = itemData.TextId;
+            icon.sprite = itemData.Sprite;
+            //title.text = itemData.Name;
+            if (itemData.LanguageType == 0)
+            {
+                title.gameObject.AddComponent<TextAutoLanguage>().SetText(int.Parse(itemData.TextId));
+            }
+            else {
+                title.gameObject.AddComponent<TextAutoLanguage2>().SetTextKey(itemData.TextId);
+            }
+            var selected = Context.SelectedIndex == Index;
+            line.SetActive(selected);
+            title.gameObject.SetActive(selected);
+            //image.color = selected
+            //    ? new Color32(0, 255, 255, 100)
+            //    : new Color32(255, 255, 255, 77);
+        }
+
+        public override void UpdatePosition(float position)
+        {
+            currentPosition = position;
+
+
+            if (animator.isActiveAndEnabled)
+            {
+                animator.Play(AnimatorHash.Scroll, -1, position);
+            }
+
+            animator.speed = 0;
+        }
+
+        // GameObject が非アクティブになると Animator がリセットされてしまうため
+        // 現在位置を保持しておいて OnEnable のタイミングで現在位置を再設定します
+        float currentPosition = 0;
+
+        void OnEnable() => UpdatePosition(currentPosition);
+    }
+}

+ 12 - 0
Assets/BowArrow/FancyScrollView/FocusOn/Cell.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 08f1e4cba8817f34ba916b510b19d736
+timeCreated: 1487505842
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 16 - 0
Assets/BowArrow/FancyScrollView/FocusOn/Context.cs

@@ -0,0 +1,16 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+
+namespace FancyScrollView.FocusOn
+{
+    class Context
+    {
+        public int SelectedIndex = -1;
+        public Action<int,bool> OnCellClicked;
+    }
+}

+ 12 - 0
Assets/BowArrow/FancyScrollView/FocusOn/Context.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 1bc24697641338048b4ae874f1aac236
+timeCreated: 1487505870
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 34 - 0
Assets/BowArrow/FancyScrollView/FocusOn/ItemData.cs

@@ -0,0 +1,34 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using UnityEngine;
+
+namespace FancyScrollView.FocusOn
+{
+    class ItemData
+    {
+        public string Message { get; }
+
+        public Sprite Sprite { get; }
+
+        public string Name { get; }
+
+        public string TextId { get; }
+
+        public int LanguageType { get; }
+        public ItemData(string message)
+        {
+            Message = message;
+        }
+
+        public ItemData(string name, Sprite sprite, string textId,int languageType) {
+            Sprite = sprite;
+            Name = name;
+            TextId = textId;
+            LanguageType = languageType;
+        }
+    }
+}

+ 12 - 0
Assets/BowArrow/FancyScrollView/FocusOn/ItemData.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 48174935593094cee83cdaa0b11eb8d7
+timeCreated: 1552490564
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 101 - 0
Assets/BowArrow/FancyScrollView/FocusOn/ScrollView.cs

@@ -0,0 +1,101 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using EasingCore;
+
+namespace FancyScrollView.FocusOn
+{
+    class ScrollView : FancyScrollView<ItemData, Context>
+    {
+        [SerializeField] Scroller scroller = default;
+        [SerializeField] GameObject cellPrefab = default;
+
+        Action<int,bool> onSelectionChanged;
+
+
+        protected override GameObject CellPrefab => cellPrefab;
+
+        protected override void Initialize()
+        {
+            base.Initialize();
+
+            Context.OnCellClicked = onClick;
+
+            scroller.OnValueChanged(UpdatePosition);
+            scroller.OnSelectionChanged(UpdateSelection);
+        }
+
+        void UpdateSelection(int index)
+        {
+            if (Context.SelectedIndex == index)
+            {
+                return;
+            }
+
+            Context.SelectedIndex = index;
+            Refresh();
+
+            onSelectionChanged?.Invoke(index,false);
+        }
+
+        public void UpdateData(IList<ItemData> items)
+        {
+            UpdateContents(items);
+            scroller.SetTotalCount(items.Count);
+        }
+
+        public void OnClickEvent(Action<int> OnCellClicked) { 
+
+        }
+
+        public void OnSelectionChanged(Action<int,bool> callback)
+        {
+            onSelectionChanged = callback;
+        }
+
+        public void SelectNextCell()
+        {
+            int nextIndex = Context.SelectedIndex + 4;
+            int index = nextIndex < ItemsSource.Count ? nextIndex : nextIndex - ItemsSource.Count;
+            // Debug.LogWarning("nextIndex:" + nextIndex+ ",index:"+ index + ",ItemsSource.Count:"+ ItemsSource.Count);
+            //翻页(4个一组)
+            SelectCell(index);
+        }
+
+        public void SelectPrevCell()
+        {
+            int nextIndex = Context.SelectedIndex - 4;
+            int index = nextIndex >= 0 ? nextIndex : ItemsSource.Count + nextIndex;
+            // Debug.LogWarning("nextIndex:" + nextIndex + ",index:" + index + ",ItemsSource.Count:" + ItemsSource.Count);
+            SelectCell(index);
+        }
+
+        public void SelectCell(int index,bool bScrollTo = true)
+        {
+            if (index < 0 || index >= ItemsSource.Count || index == Context.SelectedIndex)
+            {
+                //if (index == Context.SelectedIndex) {
+                //    onSelectionChanged?.Invoke(index,true);
+                //}
+                return;
+            }
+
+            UpdateSelection(index);
+            if(bScrollTo)
+                scroller.ScrollTo(index, 0.35f, Ease.OutCubic);
+        }
+
+        public void onClick(int index, bool bSelectedAndNav = true) {
+            Context.SelectedIndex = index;
+            Refresh();
+            //点击按钮算触发
+            onSelectionChanged?.Invoke(index, bSelectedAndNav);
+        }
+    }
+}

+ 12 - 0
Assets/BowArrow/FancyScrollView/FocusOn/ScrollView.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 56f1da0ed0c4e244182dffa658d69315
+timeCreated: 1487505830
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/Sources.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: b25a49c8e71e1dd4b8cfd81a1cc3d552
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/Sources/Editor.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7958ccf430495714d8fe1d2c8d9f750b
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 124 - 0
Assets/BowArrow/FancyScrollView/Sources/Editor/ScrollerEditor.cs

@@ -0,0 +1,124 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using UnityEditor;
+using UnityEditor.AnimatedValues;
+
+// For manteinance, every new [SerializeField] variable in Scroller must be declared here
+
+namespace FancyScrollView
+{
+    [CustomEditor(typeof(Scroller))]
+    [CanEditMultipleObjects]
+    public class ScrollerEditor : Editor
+    {
+        SerializedProperty viewport;
+        SerializedProperty scrollDirection;
+        SerializedProperty movementType;
+        SerializedProperty elasticity;
+        SerializedProperty scrollSensitivity;
+        SerializedProperty inertia;
+        SerializedProperty decelerationRate;
+        SerializedProperty snap;
+        SerializedProperty draggable;
+        SerializedProperty scrollbar;
+
+        AnimBool showElasticity;
+        AnimBool showInertiaRelatedValues;
+
+        void OnEnable()
+        {
+            viewport = serializedObject.FindProperty("viewport");
+            scrollDirection = serializedObject.FindProperty("scrollDirection");
+            movementType = serializedObject.FindProperty("movementType");
+            elasticity = serializedObject.FindProperty("elasticity");
+            scrollSensitivity = serializedObject.FindProperty("scrollSensitivity");
+            inertia = serializedObject.FindProperty("inertia");
+            decelerationRate = serializedObject.FindProperty("decelerationRate");
+            snap = serializedObject.FindProperty("snap");
+            draggable = serializedObject.FindProperty("draggable");
+            scrollbar = serializedObject.FindProperty("scrollbar");
+
+            showElasticity = new AnimBool(Repaint);
+            showInertiaRelatedValues = new AnimBool(Repaint);
+            SetAnimBools(true);
+        }
+
+        void OnDisable()
+        {
+            showElasticity.valueChanged.RemoveListener(Repaint);
+            showInertiaRelatedValues.valueChanged.RemoveListener(Repaint);
+        }
+
+        void SetAnimBools(bool instant)
+        {
+            SetAnimBool(showElasticity, !movementType.hasMultipleDifferentValues && movementType.enumValueIndex == (int)MovementType.Elastic, instant);
+            SetAnimBool(showInertiaRelatedValues, !inertia.hasMultipleDifferentValues && inertia.boolValue, instant);
+        }
+
+        void SetAnimBool(AnimBool a, bool value, bool instant)
+        {
+            if (instant)
+            {
+                a.value = value;
+            }
+            else
+            {
+                a.target = value;
+            }
+        }
+
+        public override void OnInspectorGUI()
+        {
+            SetAnimBools(false);
+
+            serializedObject.Update();
+            EditorGUILayout.PropertyField(viewport);
+            EditorGUILayout.PropertyField(scrollDirection);
+            EditorGUILayout.PropertyField(movementType);
+            DrawMovementTypeRelatedValue();
+            EditorGUILayout.PropertyField(scrollSensitivity);
+            EditorGUILayout.PropertyField(inertia);
+            DrawInertiaRelatedValues();
+            EditorGUILayout.PropertyField(draggable);
+            EditorGUILayout.PropertyField(scrollbar);
+            serializedObject.ApplyModifiedProperties();
+        }
+
+        void DrawMovementTypeRelatedValue()
+        {
+            using (var group = new EditorGUILayout.FadeGroupScope(showElasticity.faded))
+            {
+                if (!group.visible)
+                {
+                    return;
+                }
+
+                using (new EditorGUI.IndentLevelScope())
+                {
+                    EditorGUILayout.PropertyField(elasticity);
+                }
+            }
+        }
+
+        void DrawInertiaRelatedValues()
+        {
+            using (var group = new EditorGUILayout.FadeGroupScope(showInertiaRelatedValues.faded))
+            {
+                if (!group.visible)
+                {
+                    return;
+                }
+
+                using (new EditorGUI.IndentLevelScope())
+                {
+                    EditorGUILayout.PropertyField(decelerationRate);
+                    EditorGUILayout.PropertyField(snap);
+                }
+            }
+        }
+    }
+}

+ 13 - 0
Assets/BowArrow/FancyScrollView/Sources/Editor/ScrollerEditor.cs.meta

@@ -0,0 +1,13 @@
+fileFormatVersion: 2
+guid: 986fda6476737da458576709b7f59ea3
+timeCreated: 1508699683
+licenseType: Pro
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e3bc7e5b69299d84a9e387542ecf33e9
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Core.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d54a0f3f91529ad438e621f743e624f3
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 76 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyCell.cs

@@ -0,0 +1,76 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using UnityEngine;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyScrollView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
+    /// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
+    /// 代わりに <see cref="FancyCell{TItemData}"/> を使用します.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <typeparam name="TContext"><see cref="Context"/> の型.</typeparam>
+    public abstract class FancyCell<TItemData, TContext> : MonoBehaviour where TContext : class, new()
+    {
+        /// <summary>
+        /// このセルで表示しているデータのインデックス.
+        /// </summary>
+        public int Index { get; set; } = -1;
+
+        /// <summary>
+        /// このセルの可視状態.
+        /// </summary>
+        public virtual bool IsVisible => gameObject.activeSelf;
+
+        /// <summary>
+        /// <see cref="FancyScrollView{TItemData, TContext}.Context"/> の参照.
+        /// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します.
+        /// </summary>
+        protected TContext Context { get; private set; }
+
+        /// <summary>
+        /// <see cref="Context"/> をセットします.
+        /// </summary>
+        /// <param name="context">コンテキスト.</param>
+        public virtual void SetContext(TContext context) => Context = context;
+
+        /// <summary>
+        /// 初期化を行います.
+        /// </summary>
+        public virtual void Initialize() { }
+
+        /// <summary>
+        /// このセルの可視状態を設定します.
+        /// </summary>
+        /// <param name="visible">可視状態なら <c>true</c>, 非可視状態なら <c>false</c>.</param>
+        public virtual void SetVisible(bool visible) => gameObject.SetActive(visible);
+
+        /// <summary>
+        /// アイテムデータに基づいてこのセルの表示内容を更新します.
+        /// </summary>
+        /// <param name="itemData">アイテムデータ.</param>
+        public abstract void UpdateContent(TItemData itemData);
+
+        /// <summary>
+        /// <c>0.0f</c> ~ <c>1.0f</c> の値に基づいてこのセルのスクロール位置を更新します.
+        /// </summary>
+        /// <param name="position">ビューポート範囲の正規化されたスクロール位置.</param>
+        public abstract void UpdatePosition(float position);
+    }
+
+    /// <summary>
+    /// <see cref="FancyScrollView{TItemData}"/> のセルを実装するための抽象基底クラス.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <seealso cref="FancyCell{TItemData, TContext}"/>
+    public abstract class FancyCell<TItemData> : FancyCell<TItemData, NullContext>
+    {
+        /// <inheritdoc/>
+        public sealed override void SetContext(NullContext context) => base.SetContext(context);
+    }
+}

+ 12 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyCell.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 23e073f093082c6449696d1199adc881
+timeCreated: 1487183338
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 220 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyScrollView.cs

@@ -0,0 +1,220 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// スクロールビューを実装するための抽象基底クラス.
+    /// 無限スクロールおよびスナップに対応しています.
+    /// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
+    /// 代わりに <see cref="FancyScrollView{TItemData}"/> を使用します.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <typeparam name="TContext"><see cref="Context"/> の型.</typeparam>
+    public abstract class FancyScrollView<TItemData, TContext> : MonoBehaviour where TContext : class, new()
+    {
+        /// <summary>
+        /// セル同士の間隔.
+        /// </summary>
+        [SerializeField, Range(1e-2f, 1f)] protected float cellInterval = 0.2f;
+
+        /// <summary>
+        /// スクロール位置の基準.
+        /// </summary>
+        /// <remarks>
+        /// たとえば、 <c>0.5</c> を指定してスクロール位置が <c>0</c> の場合, 中央に最初のセルが配置されます.
+        /// </remarks>
+        [SerializeField, Range(0f, 1f)] protected float scrollOffset = 0.5f;
+
+        /// <summary>
+        /// セルを循環して配置させるどうか.
+        /// </summary>
+        /// <remarks>
+        /// <c>true</c> にすると最後のセルの後に最初のセル, 最初のセルの前に最後のセルが並ぶようになります.
+        /// 無限スクロールを実装する場合は <c>true</c> を指定します.
+        /// </remarks>
+        [SerializeField] protected bool loop = false;
+
+        /// <summary>
+        /// セルの親要素となる <c>Transform</c>.
+        /// </summary>
+        [SerializeField] protected Transform cellContainer = default;
+
+        readonly IList<FancyCell<TItemData, TContext>> pool = new List<FancyCell<TItemData, TContext>>();
+
+        /// <summary>
+        /// 初期化済みかどうか.
+        /// </summary>
+        protected bool initialized;
+
+        /// <summary>
+        /// 現在のスクロール位置.
+        /// </summary>
+        protected float currentPosition;
+
+        /// <summary>
+        /// セルの Prefab.
+        /// </summary>
+        protected abstract GameObject CellPrefab { get; }
+
+        /// <summary>
+        /// アイテム一覧のデータ.
+        /// </summary>
+        protected IList<TItemData> ItemsSource { get; set; } = new List<TItemData>();
+
+        /// <summary>
+        /// <typeparamref name="TContext"/> のインスタンス.
+        /// セルとスクロールビュー間で同じインスタンスが共有されます. 情報の受け渡しや状態の保持に使用します.
+        /// </summary>
+        protected TContext Context { get; } = new TContext();
+
+        /// <summary>
+        /// 初期化を行います.
+        /// </summary>
+        /// <remarks>
+        /// 最初にセルが生成される直前に呼び出されます.
+        /// </remarks>
+        protected virtual void Initialize() { }
+
+        /// <summary>
+        /// 渡されたアイテム一覧に基づいて表示内容を更新します.
+        /// </summary>
+        /// <param name="itemsSource">アイテム一覧.</param>
+        protected virtual void UpdateContents(IList<TItemData> itemsSource)
+        {
+            ItemsSource = itemsSource;
+            Refresh();
+        }
+
+        /// <summary>
+        /// セルのレイアウトを強制的に更新します.
+        /// </summary>
+        protected virtual void Relayout() => UpdatePosition(currentPosition, false);
+
+        /// <summary>
+        /// セルのレイアウトと表示内容を強制的に更新します.
+        /// </summary>
+        protected virtual void Refresh() => UpdatePosition(currentPosition, true);
+
+        /// <summary>
+        /// スクロール位置を更新します.
+        /// </summary>
+        /// <param name="position">スクロール位置.</param>
+        protected virtual void UpdatePosition(float position) => UpdatePosition(position, false);
+
+        void UpdatePosition(float position, bool forceRefresh)
+        {
+            if (!initialized)
+            {
+                Initialize();
+                initialized = true;
+            }
+
+            currentPosition = position;
+
+            var p = position - scrollOffset / cellInterval;
+            var firstIndex = Mathf.CeilToInt(p);
+            var firstPosition = (Mathf.Ceil(p) - p) * cellInterval;
+
+            if (firstPosition + pool.Count * cellInterval < 1f)
+            {
+                ResizePool(firstPosition);
+            }
+
+            UpdateCells(firstPosition, firstIndex, forceRefresh);
+        }
+
+        void ResizePool(float firstPosition)
+        {
+            Debug.Assert(CellPrefab != null);
+            Debug.Assert(cellContainer != null);
+
+            var addCount = Mathf.CeilToInt((1f - firstPosition) / cellInterval) - pool.Count;
+            for (var i = 0; i < addCount; i++)
+            {
+                var cell = Instantiate(CellPrefab, cellContainer).GetComponent<FancyCell<TItemData, TContext>>();
+                if (cell == null)
+                {
+                    throw new MissingComponentException(string.Format(
+                        "FancyCell<{0}, {1}> component not found in {2}.",
+                        typeof(TItemData).FullName, typeof(TContext).FullName, CellPrefab.name));
+                }
+
+                cell.SetContext(Context);
+                cell.Initialize();
+                cell.SetVisible(false);
+                pool.Add(cell);
+            }
+        }
+
+        void UpdateCells(float firstPosition, int firstIndex, bool forceRefresh)
+        {
+            for (var i = 0; i < pool.Count; i++)
+            {
+                var index = firstIndex + i;
+                var position = firstPosition + i * cellInterval;
+                var cell = pool[CircularIndex(index, pool.Count)];
+
+                if (loop)
+                {
+                    index = CircularIndex(index, ItemsSource.Count);
+                }
+
+                if (index < 0 || index >= ItemsSource.Count || position > 1f)
+                {
+                    cell.SetVisible(false);
+                    continue;
+                }
+
+                if (forceRefresh || cell.Index != index || !cell.IsVisible)
+                {
+                    cell.Index = index;
+                    cell.SetVisible(true);
+                    cell.UpdateContent(ItemsSource[index]);
+                }
+
+                cell.UpdatePosition(position);
+            }
+        }
+
+        int CircularIndex(int i, int size) => size < 1 ? 0 : i < 0 ? size - 1 + (i + 1) % size : i % size;
+
+#if UNITY_EDITOR
+        bool cachedLoop;
+        float cachedCellInterval, cachedScrollOffset;
+
+        void LateUpdate()
+        {
+            if (cachedLoop != loop ||
+                cachedCellInterval != cellInterval ||
+                cachedScrollOffset != scrollOffset)
+            {
+                cachedLoop = loop;
+                cachedCellInterval = cellInterval;
+                cachedScrollOffset = scrollOffset;
+
+                UpdatePosition(currentPosition);
+            }
+        }
+#endif
+    }
+
+    /// <summary>
+    /// <see cref="FancyScrollView{TItemData}"/> のコンテキストクラス.
+    /// </summary>
+    public sealed class NullContext { }
+
+    /// <summary>
+    /// スクロールビューを実装するための抽象基底クラス.
+    /// 無限スクロールおよびスナップに対応しています.
+    /// </summary>
+    /// <typeparam name="TItemData"></typeparam>
+    /// <seealso cref="FancyScrollView{TItemData, TContext}"/>
+    public abstract class FancyScrollView<TItemData> : FancyScrollView<TItemData, NullContext> { }
+}

+ 12 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Core/FancyScrollView.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: e5a78e3955a0bca4593487fdd78cf2d4
+timeCreated: 1487183318
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a231aec9689dcca4ba4e8933f9737003
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 76 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyCellGroup.cs

@@ -0,0 +1,76 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using UnityEngine;
+using System.Linq;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// 複数の <see cref="FancyCell{TItemData, TContext}"/> を持つセルグループ実装するための抽象基底クラス.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
+    public abstract class FancyCellGroup<TItemData, TContext> : FancyCell<TItemData[], TContext>
+        where TContext : class, IFancyCellGroupContext, new()
+    {
+        /// <summary>
+        /// このグループで表示するセルの配列.
+        /// </summary>
+        protected virtual FancyCell<TItemData, TContext>[] Cells { get; private set; }
+
+        /// <summary>
+        /// このグループで表示するセルの配列をインスタンス化します.
+        /// </summary>
+        /// <returns>このグループで表示するセルの配列.</returns>
+        protected virtual FancyCell<TItemData, TContext>[] InstantiateCells()
+        {
+            return Enumerable.Range(0, Context.GetGroupCount())
+                .Select(_ => Instantiate(Context.CellTemplate, transform))
+                .Select(x => x.GetComponent<FancyCell<TItemData, TContext>>())
+                .ToArray();
+        }
+
+        /// <inheritdoc/>
+        public override void Initialize()
+        {
+            Cells = InstantiateCells();
+            Debug.Assert(Cells.Length == Context.GetGroupCount());
+
+            for (var i = 0; i < Cells.Length; i++)
+            {
+                Cells[i].SetContext(Context);
+                Cells[i].Initialize();
+            }
+        }
+
+        /// <inheritdoc/>
+        public override void UpdateContent(TItemData[] contents)
+        {
+            var firstCellIndex = Index * Context.GetGroupCount();
+
+            for (var i = 0; i < Cells.Length; i++)
+            {
+                Cells[i].Index = i + firstCellIndex;
+                Cells[i].SetVisible(i < contents.Length);
+
+                if (Cells[i].IsVisible)
+                {
+                    Cells[i].UpdateContent(contents[i]);
+                }
+            }
+        }
+
+        /// <inheritdoc/>
+        public override void UpdatePosition(float position)
+        {
+            for (var i = 0; i < Cells.Length; i++)
+            {
+                Cells[i].UpdatePosition(position);
+            }
+        }
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyCellGroup.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 84300901ad8704c11b39587ed6d87468
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 185 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridView.cs

@@ -0,0 +1,185 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+using System.Linq;
+using System.Collections.Generic;
+using UnityEngine;
+using EasingCore;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
+    /// 無限スクロールおよびスナップには対応していません.
+    /// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
+    /// 代わりに <see cref="FancyGridView{TItemData}"/> を使用します.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <typeparam name="TContext"><see cref="FancyScrollView{TItemData, TContext}.Context"/> の型.</typeparam>
+    public abstract class FancyGridView<TItemData, TContext> : FancyScrollRect<TItemData[], TContext>
+        where TContext : class, IFancyGridViewContext, new()
+    {
+        /// <summary>
+        /// デフォルトのセルグループクラス.
+        /// </summary>
+        protected abstract class DefaultCellGroup : FancyCellGroup<TItemData, TContext> { }
+
+        /// <summary>
+        /// 最初にセルを配置する軸方向のセル同士の余白.
+        /// </summary>
+        [SerializeField] protected float startAxisSpacing = 0f;
+
+        /// <summary>
+        /// 最初にセルを配置する軸方向のセル数.
+        /// </summary>
+        [SerializeField] protected int startAxisCellCount = 4;
+
+        /// <summary>
+        /// セルのサイズ.
+        /// </summary>
+        [SerializeField] protected Vector2 cellSize = new Vector2(100f, 100f);
+
+        /// <summary>
+        /// セルのグループ Prefab.
+        /// </summary>
+        /// <remarks>
+        /// <see cref="FancyGridView{TItemData, TContext}"/> では,
+        /// <see cref="FancyScrollView{TItemData, TContext}.CellPrefab"/> を最初にセルを配置する軸方向のセルコンテナとして使用します.
+        /// </remarks>
+        protected sealed override GameObject CellPrefab => cellGroupTemplate;
+
+        /// <inheritdoc/>
+        protected override float CellSize => Scroller.ScrollDirection == ScrollDirection.Horizontal
+            ? cellSize.x
+            : cellSize.y;
+
+        /// <summary>
+        /// アイテムの総数.
+        /// </summary>
+        public int DataCount { get; private set; }
+
+        GameObject cellGroupTemplate;
+
+        /// <inheritdoc/>
+        protected override void Initialize()
+        {
+            base.Initialize();
+
+            Debug.Assert(startAxisCellCount > 0);
+
+            Context.ScrollDirection = Scroller.ScrollDirection;
+            Context.GetGroupCount = () => startAxisCellCount;
+            Context.GetStartAxisSpacing = () => startAxisSpacing;
+            Context.GetCellSize = () => Scroller.ScrollDirection == ScrollDirection.Horizontal
+                ? cellSize.y
+                : cellSize.x;
+
+            SetupCellTemplate();
+        }
+
+        /// <summary>
+        /// 最初にセルが生成される直前に呼び出されます.
+        /// <see cref="Setup{TGroup}(FancyCell{TItemData, TContext})"/> メソッドを使用してセルテンプレートのセットアップを行ってください.
+        /// </summary>
+        /// <example>
+        /// <code><![CDATA[
+        /// using UnityEngine;
+        /// using FancyScrollView;
+        /// 
+        /// public class MyGridView : FancyGridView<ItemData, Context>
+        /// {
+        ///     class CellGroup : DefaultCellGroup { }
+        /// 
+        ///     [SerializeField] Cell cellPrefab = default;
+        /// 
+        ///     protected override void SetupCellTemplate() => Setup<CellGroup>(cellPrefab);
+        /// }
+        /// ]]></code>
+        /// </example>
+        protected abstract void SetupCellTemplate();
+
+        /// <summary>
+        /// セルテンプレートのセットアップを行います.
+        /// </summary>
+        /// <param name="cellTemplate">セルのテンプレート.</param>
+        /// <typeparam name="TGroup">セルグループの型.</typeparam>
+        protected virtual void Setup<TGroup>(FancyCell<TItemData, TContext> cellTemplate)
+            where TGroup : FancyCell<TItemData[], TContext>
+        {
+            Context.CellTemplate = cellTemplate.gameObject;
+
+            cellGroupTemplate = new GameObject("Group").AddComponent<TGroup>().gameObject;
+            cellGroupTemplate.transform.SetParent(cellContainer, false);
+            cellGroupTemplate.SetActive(false);
+        }
+
+        /// <summary>
+        /// 渡されたアイテム一覧に基づいて表示内容を更新します.
+        /// </summary>
+        /// <param name="items">アイテム一覧.</param>
+        public virtual void UpdateContents(IList<TItemData> items)
+        {
+            DataCount = items.Count;
+
+            var itemGroups = items
+                .Select((item, index) => (item, index))
+                .GroupBy(
+                    x => x.index / startAxisCellCount,
+                    x => x.item)
+                .Select(group => group.ToArray())
+                .ToArray();
+
+            UpdateContents(itemGroups);
+        }
+
+        /// <summary>
+        /// 指定したアイテムの位置までジャンプします.
+        /// </summary>
+        /// <param name="itemIndex">アイテムのインデックス.</param>
+        /// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
+        protected override void JumpTo(int itemIndex, float alignment = 0.5f)
+        {
+            var groupIndex = itemIndex / startAxisCellCount;
+            base.JumpTo(groupIndex, alignment);
+        }
+
+        /// <summary>
+        /// 指定したアイテムの位置まで移動します.
+        /// </summary>
+        /// <param name="itemIndex">アイテムのインデックス.</param>
+        /// <param name="duration">移動にかける秒数.</param>
+        /// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
+        /// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
+        protected override void ScrollTo(int itemIndex, float duration, float alignment = 0.5f, Action onComplete = null)
+        {
+            var groupIndex = itemIndex / startAxisCellCount;
+            base.ScrollTo(groupIndex, duration, alignment, onComplete);
+        }
+
+        /// <summary>
+        /// 指定したアイテムの位置まで移動します.
+        /// </summary>
+        /// <param name="itemIndex">アイテムのインデックス.</param>
+        /// <param name="duration">移動にかける秒数.</param>
+        /// <param name="easing">移動に使用するイージング.</param>
+        /// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
+        /// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
+        protected override void ScrollTo(int itemIndex, float duration, Ease easing, float alignment = 0.5f, Action onComplete = null)
+        {
+            var groupIndex = itemIndex / startAxisCellCount;
+            base.ScrollTo(groupIndex, duration, easing, alignment, onComplete);
+        }
+    }
+
+    /// <summary>
+    /// グリッドレイアウトのスクロールビューを実装するための抽象基底クラス.
+    /// 無限スクロールおよびスナップには対応していません.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <seealso cref="FancyGridView{TItemData, TContext}"/>
+    public abstract class FancyGridView<TItemData> : FancyGridView<TItemData, FancyGridViewContext> { }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridView.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e806c8d18b1ff45bb87e9a5b87ec85e3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 47 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewCell.cs

@@ -0,0 +1,47 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using UnityEngine;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyGridView{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
+    /// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
+    /// 代わりに <see cref="FancyGridViewCell{TItemData}"/> を使用します.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
+    public abstract class FancyGridViewCell<TItemData, TContext> : FancyScrollRectCell<TItemData, TContext>
+        where TContext : class, IFancyGridViewContext, new()
+    {
+        /// <inheritdoc/>
+        protected override void UpdatePosition(float normalizedPosition, float localPosition)
+        {
+            var cellSize = Context.GetCellSize();
+            var spacing = Context.GetStartAxisSpacing();
+            var groupCount = Context.GetGroupCount();
+
+            var indexInGroup = Index % groupCount;
+            var positionInGroup = (cellSize + spacing) * (indexInGroup - (groupCount - 1) * 0.5f);
+
+            transform.localPosition = Context.ScrollDirection == ScrollDirection.Horizontal
+                ? new Vector2(-localPosition, -positionInGroup)
+                : new Vector2(positionInGroup, localPosition);
+        }
+    }
+
+    /// <summary>
+    /// <see cref="FancyGridView{TItemData}"/> のセルを実装するための抽象基底クラス.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <seealso cref="FancyGridViewCell{TItemData, TContext}"/>
+    public abstract class FancyGridViewCell<TItemData> : FancyGridViewCell<TItemData, FancyGridViewContext>
+    {
+        /// <inheritdoc/>
+        public sealed override void SetContext(FancyGridViewContext context) => base.SetContext(context);
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewCell.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 129bb13f5b2b9435a93d53ca93471334
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 24 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewContext.cs

@@ -0,0 +1,24 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+using UnityEngine;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキスト基底クラス.
+    /// </summary>
+    public class FancyGridViewContext : IFancyGridViewContext
+    {
+        ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
+        Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { get; set; }
+        GameObject IFancyCellGroupContext.CellTemplate { get; set; }
+        Func<int> IFancyCellGroupContext.GetGroupCount { get; set; }
+        Func<float> IFancyGridViewContext.GetStartAxisSpacing { get; set; }
+        Func<float> IFancyGridViewContext.GetCellSize { get; set; }
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/FancyGridViewContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 954a17398bfb54ee7baac3d7ab7e822c
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 20 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyCellGroupContext.cs

@@ -0,0 +1,20 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+using UnityEngine;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyCellGroup{TItemData, TContext}"/> のコンテキストインターフェース.
+    /// </summary>
+    public interface IFancyCellGroupContext
+    {
+        GameObject CellTemplate { get; set; }
+        Func<int> GetGroupCount { get; set; }
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyCellGroupContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 213da42f7c9a347beaa56c5886c582a1
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyGridViewContext.cs

@@ -0,0 +1,19 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyGridView{TItemData, TContext}"/> のコンテキストインターフェース.
+    /// </summary>
+    public interface IFancyGridViewContext : IFancyScrollRectContext, IFancyCellGroupContext
+    {
+        Func<float> GetStartAxisSpacing { get; set; }
+        Func<float> GetCellSize { get; set ; }
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/GridView/IFancyGridViewContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3cf2d53d9c81945c28f7c558a7c40ba3
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 5f838004796424e409ff816ae1b9e755
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 305 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRect.cs

@@ -0,0 +1,305 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+using System.Collections.Generic;
+using UnityEngine;
+using EasingCore;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// ScrollRect スタイルのスクロールビューを実装するための抽象基底クラス.
+    /// 無限スクロールおよびスナップには対応していません.
+    /// <see cref="FancyScrollView{TItemData, TContext}.Context"/> が不要な場合は
+    /// 代わりに <see cref="FancyScrollRect{TItemData}"/> を使用します.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <typeparam name="TContext"><see cref="FancyScrollView{TItemData, TContext}.Context"/> の型.</typeparam>
+    [RequireComponent(typeof(Scroller))]
+    public abstract class FancyScrollRect<TItemData, TContext> : FancyScrollView<TItemData, TContext>
+        where TContext : class, IFancyScrollRectContext, new()
+    {
+        /// <summary>
+        /// スクロール中にセルが再利用されるまでの余白のセル数.
+        /// </summary>
+        /// <remarks>
+        /// <c>0</c> を指定するとセルが完全に隠れた直後に再利用されます.
+        /// <c>1</c> 以上を指定すると, そのセル数だけ余分にスクロールしてから再利用されます.
+        /// </remarks>
+        [SerializeField] protected float reuseCellMarginCount = 0f;
+
+        /// <summary>
+        /// コンテンツ先頭の余白.
+        /// </summary>
+        [SerializeField] protected float paddingHead = 0f;
+
+        /// <summary>
+        /// コンテンツ末尾の余白.
+        /// </summary>
+        [SerializeField] protected float paddingTail = 0f;
+
+        /// <summary>
+        /// スクロール軸方向のセル同士の余白.
+        /// </summary>
+        [SerializeField] protected float spacing = 0f;
+
+        /// <summary>
+        /// セルのサイズ.
+        /// </summary>
+        protected abstract float CellSize { get; }
+
+        /// <summary>
+        /// スクロール可能かどうか.
+        /// </summary>
+        /// <remarks>
+        /// アイテム数が十分少なくビューポート内に全てのセルが収まっている場合は <c>false</c>, それ以外は <c>true</c> になります.
+        /// </remarks>
+        protected virtual bool Scrollable => MaxScrollPosition > 0f;
+
+        Scroller cachedScroller;
+
+        /// <summary>
+        /// スクロール位置を制御する <see cref="FancyScrollView.Scroller"/> のインスタンス.
+        /// </summary>
+        /// <remarks>
+        /// <see cref="Scroller"/> のスクロール位置を変更する際は必ず <see cref="ToScrollerPosition(float)"/> を使用して変換した位置を使用してください.
+        /// </remarks>
+        protected Scroller Scroller => cachedScroller ?? (cachedScroller = GetComponent<Scroller>());
+
+        float ScrollLength => 1f / Mathf.Max(cellInterval, 1e-2f) - 1f;
+
+        float ViewportLength => ScrollLength - reuseCellMarginCount * 2f;
+
+        float PaddingHeadLength => (paddingHead - spacing * 0.5f) / (CellSize + spacing);
+
+        float MaxScrollPosition => ItemsSource.Count
+            - ScrollLength
+            + reuseCellMarginCount * 2f
+            + (paddingHead + paddingTail - spacing) / (CellSize + spacing);
+
+        /// <inheritdoc/>
+        protected override void Initialize()
+        {
+            base.Initialize();
+
+            Context.ScrollDirection = Scroller.ScrollDirection;
+            Context.CalculateScrollSize = () =>
+            {
+                var interval = CellSize + spacing;
+                var reuseMargin = interval * reuseCellMarginCount;
+                var scrollSize = Scroller.ViewportSize + interval + reuseMargin * 2f;
+                return (scrollSize, reuseMargin);
+            };
+
+            AdjustCellIntervalAndScrollOffset();
+            Scroller.OnValueChanged(OnScrollerValueChanged);
+        }
+
+        /// <summary>
+        /// <see cref="Scroller"/> のスクロール位置が変更された際の処理.
+        /// </summary>
+        /// <param name="p"><see cref="Scroller"/> のスクロール位置.</param>
+        void OnScrollerValueChanged(float p)
+        {
+            base.UpdatePosition(ToFancyScrollViewPosition(Scrollable ? p : 0f));
+
+            if (Scroller.Scrollbar)
+            {
+                if (p > ItemsSource.Count - 1)
+                {
+                    ShrinkScrollbar(p - (ItemsSource.Count - 1));
+                }
+                else if (p < 0f)
+                {
+                    ShrinkScrollbar(-p);
+                }
+            }
+        }
+
+        /// <summary>
+        /// スクロール範囲を超えてスクロールされた量に基づいて, スクロールバーのサイズを縮小します.
+        /// </summary>
+        /// <param name="offset">スクロール範囲を超えてスクロールされた量.</param>
+        void ShrinkScrollbar(float offset)
+        {
+            var scale = 1f - ToFancyScrollViewPosition(offset) / (ViewportLength - PaddingHeadLength);
+            UpdateScrollbarSize((ViewportLength - PaddingHeadLength) * scale);
+        }
+
+        /// <inheritdoc/>
+        protected override void Refresh()
+        {
+            AdjustCellIntervalAndScrollOffset();
+            RefreshScroller();
+            base.Refresh();
+        }
+
+        /// <inheritdoc/>
+        protected override void Relayout()
+        {
+            AdjustCellIntervalAndScrollOffset();
+            RefreshScroller();
+            base.Relayout();
+        }
+
+        /// <summary>
+        /// <see cref="Scroller"/> の各種状態を更新します.
+        /// </summary>
+        protected void RefreshScroller()
+        {
+            Scroller.Draggable = Scrollable;
+            Scroller.ScrollSensitivity = ToScrollerPosition(ViewportLength - PaddingHeadLength);
+            Scroller.Position = ToScrollerPosition(currentPosition);
+
+            if (Scroller.Scrollbar)
+            {
+                Scroller.Scrollbar.gameObject.SetActive(Scrollable);
+                UpdateScrollbarSize(ViewportLength);
+            }
+        }
+
+        /// <inheritdoc/>
+        protected override void UpdateContents(IList<TItemData> items)
+        {
+            AdjustCellIntervalAndScrollOffset();
+            base.UpdateContents(items);
+
+            Scroller.SetTotalCount(items.Count);
+            RefreshScroller();
+        }
+
+        /// <summary>
+        /// スクロール位置を更新します.
+        /// </summary>
+        /// <param name="position">スクロール位置.</param>
+        protected new void UpdatePosition(float position)
+        {
+            Scroller.Position = ToScrollerPosition(position, 0.5f);
+        }
+
+        /// <summary>
+        /// 指定したアイテムの位置までジャンプします.
+        /// </summary>
+        /// <param name="itemIndex">アイテムのインデックス.</param>
+        /// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
+        protected virtual void JumpTo(int itemIndex, float alignment = 0.5f)
+        {
+            Scroller.Position = ToScrollerPosition(itemIndex, alignment);
+        }
+
+        /// <summary>
+        /// 指定したアイテムの位置まで移動します.
+        /// </summary>
+        /// <param name="index">アイテムのインデックス.</param>
+        /// <param name="duration">移動にかける秒数.</param>
+        /// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
+        /// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
+        protected virtual void ScrollTo(int index, float duration, float alignment = 0.5f, Action onComplete = null)
+        {
+            Scroller.ScrollTo(ToScrollerPosition(index, alignment), duration, onComplete);
+        }
+
+        /// <summary>
+        /// 指定したアイテムの位置まで移動します.
+        /// </summary>
+        /// <param name="index">アイテムのインデックス.</param>
+        /// <param name="duration">移動にかける秒数.</param>
+        /// <param name="easing">移動に使用するイージング.</param>
+        /// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
+        /// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
+        protected virtual void ScrollTo(int index, float duration, Ease easing, float alignment = 0.5f, Action onComplete = null)
+        {
+            Scroller.ScrollTo(ToScrollerPosition(index, alignment), duration, easing, onComplete);
+        }
+
+        /// <summary>
+        /// ビューポートとコンテンツの長さに基づいてスクロールバーのサイズを更新します.
+        /// </summary>
+        /// <param name="viewportLength">ビューポートのサイズ.</param>
+        protected void UpdateScrollbarSize(float viewportLength)
+        {
+            var contentLength = Mathf.Max(ItemsSource.Count + (paddingHead + paddingTail - spacing) / (CellSize + spacing), 1);
+            Scroller.Scrollbar.size = Scrollable ? Mathf.Clamp01(viewportLength / contentLength) : 1f;
+        }
+
+        /// <summary>
+        /// <see cref="Scroller"/> が扱うスクロール位置を <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置に変換します.
+        /// </summary>
+        /// <param name="position"><see cref="Scroller"/> が扱うスクロール位置.</param>
+        /// <returns><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</returns>
+        protected float ToFancyScrollViewPosition(float position)
+        {
+            return position / Mathf.Max(ItemsSource.Count - 1, 1) * MaxScrollPosition - PaddingHeadLength;
+        }
+
+        /// <summary>
+        /// <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置を <see cref="Scroller"/> が扱うスクロール位置に変換します.
+        /// </summary>
+        /// <param name="position"><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</param>
+        /// <returns><see cref="Scroller"/> が扱うスクロール位置.</returns>
+        protected float ToScrollerPosition(float position)
+        {
+            return (position + PaddingHeadLength) / MaxScrollPosition * Mathf.Max(ItemsSource.Count - 1, 1);
+        }
+
+        /// <summary>
+        /// <see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置を <see cref="Scroller"/> が扱うスクロール位置に変換します.
+        /// </summary>
+        /// <param name="position"><see cref="FancyScrollRect{TItemData, TContext}"/> が扱うスクロール位置.</param>
+        /// <param name="alignment">ビューポート内におけるセル位置の基準. 0f(先頭) ~ 1f(末尾).</param>
+        /// <returns><see cref="Scroller"/> が扱うスクロール位置.</returns>
+        protected float ToScrollerPosition(float position, float alignment = 0.5f)
+        {
+            var offset = alignment * (ScrollLength - (1f + reuseCellMarginCount * 2f))
+                + (1f - alignment - 0.5f) * spacing / (CellSize + spacing);
+            return ToScrollerPosition(Mathf.Clamp(position - offset, 0f, MaxScrollPosition));
+        }
+
+        /// <summary>
+        /// 指定された設定を実現するための
+        /// <see cref="FancyScrollView{TItemData,TContext}.cellInterval"/> と
+        /// <see cref="FancyScrollView{TItemData,TContext}.scrollOffset"/> を計算して適用します.
+        /// </summary>
+        protected void AdjustCellIntervalAndScrollOffset()
+        {
+            var totalSize = Scroller.ViewportSize + (CellSize + spacing) * (1f + reuseCellMarginCount * 2f);
+            cellInterval = (CellSize + spacing) / totalSize;
+            scrollOffset = cellInterval * (1f + reuseCellMarginCount);
+        }
+
+        protected virtual void OnValidate()
+        {
+            AdjustCellIntervalAndScrollOffset();
+
+            if (loop)
+            {
+                loop = false;
+                Debug.LogError("Loop is currently not supported in FancyScrollRect.");
+            }
+
+            if (Scroller.SnapEnabled)
+            {
+                Scroller.SnapEnabled = false;
+                Debug.LogError("Snap is currently not supported in FancyScrollRect.");
+            }
+
+            if (Scroller.MovementType == MovementType.Unrestricted)
+            {
+                Scroller.MovementType = MovementType.Elastic;
+                Debug.LogError("MovementType.Unrestricted is currently not supported in FancyScrollRect.");
+            }
+        }
+    }
+
+    /// <summary>
+    /// ScrollRect スタイルのスクロールビューを実装するための抽象基底クラス.
+    /// 無限スクロールおよびスナップには対応していません.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <seealso cref="FancyScrollRect{TItemData, TContext}"/>
+    public abstract class FancyScrollRect<TItemData> : FancyScrollRect<TItemData, FancyScrollRectContext> { }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRect.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 66c8eb84fdbde4a4a8273b98227a282d
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 61 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectCell.cs

@@ -0,0 +1,61 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using UnityEngine;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyScrollRect{TItemData, TContext}"/> のセルを実装するための抽象基底クラス.
+    /// <see cref="FancyCell{TItemData, TContext}.Context"/> が不要な場合は
+    /// 代わりに <see cref="FancyScrollRectCell{TItemData}"/> を使用します.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <typeparam name="TContext"><see cref="FancyCell{TItemData, TContext}.Context"/> の型.</typeparam>
+    public abstract class FancyScrollRectCell<TItemData, TContext> : FancyCell<TItemData, TContext>
+        where TContext : class, IFancyScrollRectContext, new()
+    {
+        /// <inheritdoc/>
+        public override void UpdatePosition(float position)
+        {
+            var (scrollSize, reuseMargin) = Context.CalculateScrollSize();
+
+            var normalizedPosition = (Mathf.Lerp(0f, scrollSize, position) - reuseMargin) / (scrollSize - reuseMargin * 2f);
+
+            var start = 0.5f * scrollSize;
+            var end = -start;
+
+            UpdatePosition(normalizedPosition, Mathf.Lerp(start, end, position));
+        }
+
+        /// <summary>
+        /// このセルの位置を更新します.
+        /// </summary>
+        /// <param name="normalizedPosition">
+        /// ビューポートの範囲で正規化されたスクロール位置.
+        /// <see cref="FancyScrollRect{TItemData, TContext}.reuseCellMarginCount"/> の値に基づいて
+        ///  <c>0.0</c> ~ <c>1.0</c> の範囲を超えた値が渡されることがあります.
+        /// </param>
+        /// <param name="localPosition">ローカル位置.</param>
+        protected virtual void UpdatePosition(float normalizedPosition, float localPosition)
+        {
+            transform.localPosition = Context.ScrollDirection == ScrollDirection.Horizontal
+                ? new Vector2(-localPosition, 0)
+                : new Vector2(0, localPosition);
+        }
+    }
+
+    /// <summary>
+    /// <see cref="FancyScrollRect{TItemData}"/> のセルを実装するための抽象基底クラス.
+    /// </summary>
+    /// <typeparam name="TItemData">アイテムのデータ型.</typeparam>
+    /// <seealso cref="FancyScrollRectCell{TItemData, TContext}"/>
+    public abstract class FancyScrollRectCell<TItemData> : FancyScrollRectCell<TItemData, FancyScrollRectContext>
+    {
+        /// <inheritdoc/>
+        public sealed override void SetContext(FancyScrollRectContext context) => base.SetContext(context);
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectCell.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 09f137a55810740eab42e24ef242dcfa
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectContext.cs

@@ -0,0 +1,19 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyScrollRect{TItemData, TContext}"/> のコンテキスト基底クラス.
+    /// </summary>
+    public class FancyScrollRectContext : IFancyScrollRectContext
+    {
+        ScrollDirection IFancyScrollRectContext.ScrollDirection { get; set; }
+        Func<(float ScrollSize, float ReuseMargin)> IFancyScrollRectContext.CalculateScrollSize { get; set; }
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/FancyScrollRectContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 91383bd46cee541a7a03e08cfaa47c16
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 19 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/IFancyScrollRectContext.cs

@@ -0,0 +1,19 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// <see cref="FancyScrollRect{TItemData, TContext}"/> のコンテキストインターフェース.
+    /// </summary>
+    public interface IFancyScrollRectContext
+    {
+        ScrollDirection ScrollDirection { get; set; }
+        Func<(float ScrollSize, float ReuseMargin)> CalculateScrollSize { get; set; }
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/ScrollRect/IFancyScrollRectContext.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: e425de6354b6946c7a3b9f2c807b60fe
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e12c2925944064446a7189fb41136f61
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 196 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/EasingCore.cs

@@ -0,0 +1,196 @@
+/*
+ * EasingCore (https://github.com/setchi/EasingCore)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/EasingCore/blob/master/LICENSE)
+ */
+
+using UnityEngine;
+
+namespace EasingCore
+{
+    public enum Ease
+    {
+        Linear,
+        InBack,
+        InBounce,
+        InCirc,
+        InCubic,
+        InElastic,
+        InExpo,
+        InQuad,
+        InQuart,
+        InQuint,
+        InSine,
+        OutBack,
+        OutBounce,
+        OutCirc,
+        OutCubic,
+        OutElastic,
+        OutExpo,
+        OutQuad,
+        OutQuart,
+        OutQuint,
+        OutSine,
+        InOutBack,
+        InOutBounce,
+        InOutCirc,
+        InOutCubic,
+        InOutElastic,
+        InOutExpo,
+        InOutQuad,
+        InOutQuart,
+        InOutQuint,
+        InOutSine,
+    }
+
+    public delegate float EasingFunction(float t);
+
+    public static class Easing
+    {
+        /// <summary>
+        /// Gets the easing function
+        /// </summary>
+        /// <param name="type">Ease type</param>
+        /// <returns>Easing function</returns>
+        public static EasingFunction Get(Ease type)
+        {
+            switch (type)
+            {
+                case Ease.Linear: return linear;
+                case Ease.InBack: return inBack;
+                case Ease.InBounce: return inBounce;
+                case Ease.InCirc: return inCirc;
+                case Ease.InCubic: return inCubic;
+                case Ease.InElastic: return inElastic;
+                case Ease.InExpo: return inExpo;
+                case Ease.InQuad: return inQuad;
+                case Ease.InQuart: return inQuart;
+                case Ease.InQuint: return inQuint;
+                case Ease.InSine: return inSine;
+                case Ease.OutBack: return outBack;
+                case Ease.OutBounce: return outBounce;
+                case Ease.OutCirc: return outCirc;
+                case Ease.OutCubic: return outCubic;
+                case Ease.OutElastic: return outElastic;
+                case Ease.OutExpo: return outExpo;
+                case Ease.OutQuad: return outQuad;
+                case Ease.OutQuart: return outQuart;
+                case Ease.OutQuint: return outQuint;
+                case Ease.OutSine: return outSine;
+                case Ease.InOutBack: return inOutBack;
+                case Ease.InOutBounce: return inOutBounce;
+                case Ease.InOutCirc: return inOutCirc;
+                case Ease.InOutCubic: return inOutCubic;
+                case Ease.InOutElastic: return inOutElastic;
+                case Ease.InOutExpo: return inOutExpo;
+                case Ease.InOutQuad: return inOutQuad;
+                case Ease.InOutQuart: return inOutQuart;
+                case Ease.InOutQuint: return inOutQuint;
+                case Ease.InOutSine: return inOutSine;
+                default: return linear;
+            }
+
+            float linear(float t) => t;
+
+            float inBack(float t) => t * t * t - t * Mathf.Sin(t * Mathf.PI);
+
+            float outBack(float t) => 1f - inBack(1f - t);
+
+            float inOutBack(float t) =>
+                t < 0.5f
+                    ? 0.5f * inBack(2f * t)
+                    : 0.5f * outBack(2f * t - 1f) + 0.5f;
+
+            float inBounce(float t) => 1f - outBounce(1f - t);
+
+            float outBounce(float t) =>
+                t < 4f / 11.0f ?
+                    (121f * t * t) / 16.0f :
+                t < 8f / 11.0f ?
+                    (363f / 40.0f * t * t) - (99f / 10.0f * t) + 17f / 5.0f :
+                t < 9f / 10.0f ?
+                    (4356f / 361.0f * t * t) - (35442f / 1805.0f * t) + 16061f / 1805.0f :
+                    (54f / 5.0f * t * t) - (513f / 25.0f * t) + 268f / 25.0f;
+
+            float inOutBounce(float t) =>
+                t < 0.5f
+                    ? 0.5f * inBounce(2f * t)
+                    : 0.5f * outBounce(2f * t - 1f) + 0.5f;
+
+            float inCirc(float t) => 1f - Mathf.Sqrt(1f - (t * t));
+
+            float outCirc(float t) => Mathf.Sqrt((2f - t) * t);
+
+            float inOutCirc(float t) =>
+                t < 0.5f
+                    ? 0.5f * (1 - Mathf.Sqrt(1f - 4f * (t * t)))
+                    : 0.5f * (Mathf.Sqrt(-((2f * t) - 3f) * ((2f * t) - 1f)) + 1f);
+
+            float inCubic(float t) => t * t * t;
+
+            float outCubic(float t) => inCubic(t - 1f) + 1f;
+
+            float inOutCubic(float t) =>
+                t < 0.5f
+                    ? 4f * t * t * t
+                    : 0.5f * inCubic(2f * t - 2f) + 1f;
+
+            float inElastic(float t) => Mathf.Sin(13f * (Mathf.PI * 0.5f) * t) * Mathf.Pow(2f, 10f * (t - 1f));
+
+            float outElastic(float t) => Mathf.Sin(-13f * (Mathf.PI * 0.5f) * (t + 1)) * Mathf.Pow(2f, -10f * t) + 1f;
+
+            float inOutElastic(float t) =>
+                t < 0.5f
+                    ? 0.5f * Mathf.Sin(13f * (Mathf.PI * 0.5f) * (2f * t)) * Mathf.Pow(2f, 10f * ((2f * t) - 1f))
+                    : 0.5f * (Mathf.Sin(-13f * (Mathf.PI * 0.5f) * ((2f * t - 1f) + 1f)) * Mathf.Pow(2f, -10f * (2f * t - 1f)) + 2f);
+
+            float inExpo(float t) => Mathf.Approximately(0.0f, t) ? t : Mathf.Pow(2f, 10f * (t - 1f));
+
+            float outExpo(float t) => Mathf.Approximately(1.0f, t) ? t : 1f - Mathf.Pow(2f, -10f * t);
+
+            float inOutExpo(float v) =>
+                Mathf.Approximately(0.0f, v) || Mathf.Approximately(1.0f, v)
+                    ? v
+                    : v < 0.5f
+                        ?  0.5f * Mathf.Pow(2f, (20f * v) - 10f)
+                        : -0.5f * Mathf.Pow(2f, (-20f * v) + 10f) + 1f;
+
+            float inQuad(float t) => t * t;
+
+            float outQuad(float t) => -t * (t - 2f);
+
+            float inOutQuad(float t) =>
+                t < 0.5f
+                    ?  2f * t * t
+                    : -2f * t * t + 4f * t - 1f;
+
+            float inQuart(float t) => t * t * t * t;
+
+            float outQuart(float t)
+            {
+                var u = t - 1f;
+                return u * u * u * (1f - t) + 1f;
+            }
+
+            float inOutQuart(float t) =>
+                t < 0.5f
+                    ? 8f * inQuart(t)
+                    : -8f * inQuart(t - 1f) + 1f;
+
+            float inQuint(float t) => t * t * t * t * t;
+
+            float outQuint(float t) => inQuint(t - 1f) + 1f;
+
+            float inOutQuint(float t) =>
+                t < 0.5f
+                    ? 16f * inQuint(t)
+                    : 0.5f * inQuint(2f * t - 2f) + 1f;
+
+            float inSine(float t) => Mathf.Sin((t - 1f) * (Mathf.PI * 0.5f)) + 1f;
+
+            float outSine(float t) => Mathf.Sin(t * (Mathf.PI * 0.5f));
+
+            float inOutSine(float t) => 0.5f * (1f - Mathf.Cos(t * Mathf.PI));
+        }
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/EasingCore.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3b2a35f9ff1c8487582b74620e380486
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 16 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementDirection.cs

@@ -0,0 +1,16 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+namespace FancyScrollView
+{
+    public enum MovementDirection
+    {
+        Left,
+        Right,
+        Up,
+        Down,
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementDirection.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 11769de5e972543c7a4132ceff94a5c4
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 17 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementType.cs

@@ -0,0 +1,17 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using UnityEngine.UI;
+
+namespace FancyScrollView
+{
+    public enum MovementType
+    {
+        Unrestricted = ScrollRect.MovementType.Unrestricted,
+        Elastic = ScrollRect.MovementType.Elastic,
+        Clamped = ScrollRect.MovementType.Clamped
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/MovementType.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 585de8f4f272547c89041f94f2ad92b8
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 14 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/ScrollDirection.cs

@@ -0,0 +1,14 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+namespace FancyScrollView
+{
+    public enum ScrollDirection
+    {
+        Vertical,
+        Horizontal,
+    }
+}

+ 11 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/ScrollDirection.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: 3392a30c085c642139c92e94ec0d6309
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 602 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/Scroller.cs

@@ -0,0 +1,602 @@
+/*
+ * FancyScrollView (https://github.com/setchi/FancyScrollView)
+ * Copyright (c) 2020 setchi
+ * Licensed under MIT (https://github.com/setchi/FancyScrollView/blob/master/LICENSE)
+ */
+
+using System;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.UI;
+using EasingCore;
+
+namespace FancyScrollView
+{
+    /// <summary>
+    /// スクロール位置の制御を行うコンポーネント.
+    /// </summary>
+    public class Scroller : UIBehaviour, IPointerUpHandler, IPointerDownHandler, IBeginDragHandler, IEndDragHandler, IDragHandler, IScrollHandler
+    {
+        [SerializeField] RectTransform viewport = default;
+
+        /// <summary>
+        /// ビューポートのサイズ.
+        /// </summary>
+        public float ViewportSize => scrollDirection == ScrollDirection.Horizontal
+            ? viewport.rect.size.x
+            : viewport.rect.size.y;
+
+        [SerializeField] ScrollDirection scrollDirection = ScrollDirection.Vertical;
+
+        /// <summary>
+        /// スクロール方向.
+        /// </summary>
+        public ScrollDirection ScrollDirection => scrollDirection;
+
+        [SerializeField] MovementType movementType = MovementType.Elastic;
+
+        /// <summary>
+        /// コンテンツがスクロール範囲を越えて移動するときに使用する挙動.
+        /// </summary>
+        public MovementType MovementType
+        {
+            get => movementType;
+            set => movementType = value;
+        }
+
+        [SerializeField] float elasticity = 0.1f;
+
+        /// <summary>
+        /// コンテンツがスクロール範囲を越えて移動するときに使用する弾力性の量.
+        /// </summary>
+        public float Elasticity
+        {
+            get => elasticity;
+            set => elasticity = value;
+        }
+
+        [SerializeField] float scrollSensitivity = 1f;
+
+        /// <summary>
+        /// <see cref="ViewportSize"/> の端から端まで Drag したときのスクロール位置の変化量.
+        /// </summary>
+        public float ScrollSensitivity
+        {
+            get => scrollSensitivity;
+            set => scrollSensitivity = value;
+        }
+
+        [SerializeField] bool inertia = true;
+
+        /// <summary>
+        /// 慣性を使用するかどうか. <c>true</c> を指定すると慣性が有効に, <c>false</c> を指定すると慣性が無効になります.
+        /// </summary>
+        public bool Inertia
+        {
+            get => inertia;
+            set => inertia = value;
+        }
+
+        [SerializeField] float decelerationRate = 0.03f;
+
+        /// <summary>
+        /// スクロールの減速率. <see cref="Inertia"/> が <c>true</c> の場合のみ有効です.
+        /// </summary>
+        public float DecelerationRate
+        {
+            get => decelerationRate;
+            set => decelerationRate = value;
+        }
+
+        [SerializeField] Snap snap = new Snap {
+            Enable = true,
+            VelocityThreshold = 0.5f,
+            Duration = 0.3f,
+            Easing = Ease.InOutCubic
+        };
+
+        /// <summary>
+        /// <c>true</c> ならスナップし, <c>false</c>ならスナップしません.
+        /// </summary>
+        /// <remarks>
+        /// スナップを有効にすると, 慣性でスクロールが止まる直前に最寄りのセルへ移動します.
+        /// </remarks>
+        public bool SnapEnabled
+        {
+            get => snap.Enable;
+            set => snap.Enable = value;
+        }
+
+        [SerializeField] bool draggable = true;
+
+        /// <summary>
+        /// Drag 入力を受付けるかどうか.
+        /// </summary>
+        public bool Draggable
+        {
+            get => draggable;
+            set => draggable = value;
+        }
+
+        [SerializeField] Scrollbar scrollbar = default;
+
+        /// <summary>
+        /// スクロールバーのオブジェクト.
+        /// </summary>
+        public Scrollbar Scrollbar => scrollbar;
+
+        /// <summary>
+        /// 現在のスクロール位置.
+        /// </summary>
+        /// <value></value>
+        public float Position
+        {
+            get => currentPosition;
+            set
+            {
+                autoScrollState.Reset();
+                velocity = 0f;
+                dragging = false;
+
+                UpdatePosition(value);
+            }
+        }
+
+        readonly AutoScrollState autoScrollState = new AutoScrollState();
+
+        Action<float> onValueChanged;
+        Action<int> onSelectionChanged;
+
+        Vector2 beginDragPointerPosition;
+        float scrollStartPosition;
+        float prevPosition;
+        float currentPosition;
+
+        int totalCount;
+
+        bool hold;
+        bool scrolling;
+        bool dragging;
+        float velocity;
+
+        [Serializable]
+        class Snap
+        {
+            public bool Enable;
+            public float VelocityThreshold;
+            public float Duration;
+            public Ease Easing;
+        }
+
+        static readonly EasingFunction DefaultEasingFunction = Easing.Get(Ease.OutCubic);
+
+        class AutoScrollState
+        {
+            public bool Enable;
+            public bool Elastic;
+            public float Duration;
+            public EasingFunction EasingFunction;
+            public float StartTime;
+            public float EndPosition;
+
+            public Action OnComplete;
+
+            public void Reset()
+            {
+                Enable = false;
+                Elastic = false;
+                Duration = 0f;
+                StartTime = 0f;
+                EasingFunction = DefaultEasingFunction;
+                EndPosition = 0f;
+                OnComplete = null;
+            }
+
+            public void Complete()
+            {
+                OnComplete?.Invoke();
+                Reset();
+            }
+        }
+
+        protected override void Start()
+        {
+            base.Start();
+
+            if (scrollbar)
+            {
+                scrollbar.onValueChanged.AddListener(x => UpdatePosition(x * (totalCount - 1f), false));
+            }
+        }
+
+        /// <summary>
+        /// スクロール位置が変化したときのコールバックを設定します.
+        /// </summary>
+        /// <param name="callback">スクロール位置が変化したときのコールバック.</param>
+        public void OnValueChanged(Action<float> callback) => onValueChanged = callback;
+
+        /// <summary>
+        /// 選択位置が変化したときのコールバックを設定します.
+        /// </summary>
+        /// <param name="callback">選択位置が変化したときのコールバック.</param>
+        public void OnSelectionChanged(Action<int> callback) => onSelectionChanged = callback;
+
+        /// <summary>
+        /// アイテムの総数を設定します.
+        /// </summary>
+        /// <remarks>
+        /// <paramref name="totalCount"/> を元に最大スクロール位置を計算します.
+        /// </remarks>
+        /// <param name="totalCount">アイテムの総数.</param>
+        public void SetTotalCount(int totalCount) => this.totalCount = totalCount;
+
+        /// <summary>
+        /// 指定した位置まで移動します.
+        /// </summary>
+        /// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
+        /// <param name="duration">移動にかける秒数.</param>
+        /// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
+        public void ScrollTo(float position, float duration, Action onComplete = null) => ScrollTo(position, duration, Ease.OutCubic, onComplete);
+
+        /// <summary>
+        /// 指定した位置まで移動します.
+        /// </summary>
+        /// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
+        /// <param name="duration">移動にかける秒数.</param>
+        /// <param name="easing">移動に使用するイージング.</param>
+        /// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
+        public void ScrollTo(float position, float duration, Ease easing, Action onComplete = null) => ScrollTo(position, duration, Easing.Get(easing), onComplete);
+
+        /// <summary>
+        /// 指定した位置まで移動します.
+        /// </summary>
+        /// <param name="position">スクロール位置. <c>0f</c> ~ <c>totalCount - 1f</c> の範囲.</param>
+        /// <param name="duration">移動にかける秒数.</param>
+        /// <param name="easingFunction">移動に使用するイージング関数.</param>
+        /// <param name="onComplete">移動が完了した際に呼び出されるコールバック.</param>
+        public void ScrollTo(float position, float duration, EasingFunction easingFunction, Action onComplete = null)
+        {
+            if (duration <= 0f)
+            {
+                Position = CircularPosition(position, totalCount);
+                onComplete?.Invoke();
+                return;
+            }
+
+            autoScrollState.Reset();
+            autoScrollState.Enable = true;
+            autoScrollState.Duration = duration;
+            autoScrollState.EasingFunction = easingFunction ?? DefaultEasingFunction;
+            autoScrollState.StartTime = Time.unscaledTime;
+            autoScrollState.EndPosition = currentPosition + CalculateMovementAmount(currentPosition, position);
+            autoScrollState.OnComplete = onComplete;
+
+            velocity = 0f;
+            scrollStartPosition = currentPosition;
+
+            UpdateSelection(Mathf.RoundToInt(CircularPosition(autoScrollState.EndPosition, totalCount)));
+        }
+
+        /// <summary>
+        /// 指定したインデックスの位置までジャンプします.
+        /// </summary>
+        /// <param name="index">アイテムのインデックス.</param>
+        public void JumpTo(int index)
+        {
+            if (index < 0 || index > totalCount - 1)
+            {
+                throw new ArgumentOutOfRangeException(nameof(index));
+            }
+
+            UpdateSelection(index);
+            Position = index;
+        }
+
+        /// <summary>
+        /// <paramref name="sourceIndex"/> から <paramref name="destIndex"/> に移動する際の移動方向を返します.
+        /// スクロール範囲が無制限に設定されている場合は, 最短距離の移動方向を返します.
+        /// </summary>
+        /// <param name="sourceIndex">移動元のインデックス.</param>
+        /// <param name="destIndex">移動先のインデックス.</param>
+        /// <returns></returns>
+        public MovementDirection GetMovementDirection(int sourceIndex, int destIndex)
+        {
+            var movementAmount = CalculateMovementAmount(sourceIndex, destIndex);
+            return scrollDirection == ScrollDirection.Horizontal
+                ? movementAmount > 0
+                    ? MovementDirection.Left
+                    : MovementDirection.Right
+                : movementAmount > 0
+                    ? MovementDirection.Up
+                    : MovementDirection.Down;
+        }
+
+        /// <inheritdoc/>
+        void IPointerDownHandler.OnPointerDown(PointerEventData eventData)
+        {
+            if (!draggable || eventData.button != PointerEventData.InputButton.Left)
+            {
+                return;
+            }
+
+            hold = true;
+            velocity = 0f;
+            autoScrollState.Reset();
+        }
+
+        /// <inheritdoc/>
+        void IPointerUpHandler.OnPointerUp(PointerEventData eventData)
+        {
+            if (!draggable || eventData.button != PointerEventData.InputButton.Left)
+            {
+                return;
+            }
+
+            if (hold && snap.Enable)
+            {
+                UpdateSelection(Mathf.RoundToInt(CircularPosition(currentPosition, totalCount)));
+                ScrollTo(Mathf.RoundToInt(currentPosition), snap.Duration, snap.Easing);
+            }
+
+            hold = false;
+        }
+
+        /// <inheritdoc/>
+        void IScrollHandler.OnScroll(PointerEventData eventData)
+        {
+            if (!draggable)
+            {
+                return;
+            }
+
+            var delta = eventData.scrollDelta;
+
+            // Down is positive for scroll events, while in UI system up is positive.
+            delta.y *= -1;
+            var scrollDelta = scrollDirection == ScrollDirection.Horizontal
+                ? Mathf.Abs(delta.y) > Mathf.Abs(delta.x)
+                        ? delta.y
+                        : delta.x
+                : Mathf.Abs(delta.x) > Mathf.Abs(delta.y)
+                        ? delta.x
+                        : delta.y;
+
+            if (eventData.IsScrolling())
+            {
+                scrolling = true;
+            }
+
+            var position = currentPosition + scrollDelta / ViewportSize * scrollSensitivity;
+            if (movementType == MovementType.Clamped)
+            {
+                position += CalculateOffset(position);
+            }
+
+            if (autoScrollState.Enable)
+            {
+                autoScrollState.Reset();
+            }
+
+            UpdatePosition(position);
+        }
+
+        /// <inheritdoc/>
+        void IBeginDragHandler.OnBeginDrag(PointerEventData eventData)
+        {
+            if (!draggable || eventData.button != PointerEventData.InputButton.Left)
+            {
+                return;
+            }
+
+            hold = false;
+            RectTransformUtility.ScreenPointToLocalPointInRectangle(
+                viewport,
+                eventData.position,
+                eventData.pressEventCamera,
+                out beginDragPointerPosition);
+
+            scrollStartPosition = currentPosition;
+            dragging = true;
+            autoScrollState.Reset();
+        }
+
+        /// <inheritdoc/>
+        void IDragHandler.OnDrag(PointerEventData eventData)
+        {
+            if (!draggable || eventData.button != PointerEventData.InputButton.Left || !dragging)
+            {
+                return;
+            }
+
+            if (!RectTransformUtility.ScreenPointToLocalPointInRectangle(
+                viewport,
+                eventData.position,
+                eventData.pressEventCamera,
+                out var dragPointerPosition))
+            {
+                return;
+            }
+
+            var pointerDelta = dragPointerPosition - beginDragPointerPosition;
+            var position = (scrollDirection == ScrollDirection.Horizontal ? -pointerDelta.x : pointerDelta.y)
+                           / ViewportSize
+                           * scrollSensitivity
+                           + scrollStartPosition;
+
+            var offset = CalculateOffset(position);
+            position += offset;
+
+            if (movementType == MovementType.Elastic)
+            {
+                if (offset != 0f)
+                {
+                    position -= RubberDelta(offset, scrollSensitivity);
+                }
+            }
+
+            UpdatePosition(position);
+        }
+
+        /// <inheritdoc/>
+        void IEndDragHandler.OnEndDrag(PointerEventData eventData)
+        {
+            if (!draggable || eventData.button != PointerEventData.InputButton.Left)
+            {
+                return;
+            }
+
+            dragging = false;
+        }
+
+        float CalculateOffset(float position)
+        {
+            if (movementType == MovementType.Unrestricted)
+            {
+                return 0f;
+            }
+
+            if (position < 0f)
+            {
+                return -position;
+            }
+
+            if (position > totalCount - 1)
+            {
+                return totalCount - 1 - position;
+            }
+
+            return 0f;
+        }
+
+        void UpdatePosition(float position, bool updateScrollbar = true)
+        {
+            onValueChanged?.Invoke(currentPosition = position);
+
+            if (scrollbar && updateScrollbar)
+            {
+                scrollbar.value = Mathf.Clamp01(position / Mathf.Max(totalCount - 1f, 1e-4f));
+            }
+        }
+
+        void UpdateSelection(int index) => onSelectionChanged?.Invoke(index);
+
+        float RubberDelta(float overStretching, float viewSize) =>
+            (1 - 1 / (Mathf.Abs(overStretching) * 0.55f / viewSize + 1)) * viewSize * Mathf.Sign(overStretching);
+
+        void Update()
+        {
+            var deltaTime = Time.unscaledDeltaTime;
+            var offset = CalculateOffset(currentPosition);
+
+            if (autoScrollState.Enable)
+            {
+                var position = 0f;
+
+                if (autoScrollState.Elastic)
+                {
+                    position = Mathf.SmoothDamp(currentPosition, currentPosition + offset, ref velocity,
+                        elasticity, Mathf.Infinity, deltaTime);
+
+                    if (Mathf.Abs(velocity) < 0.01f)
+                    {
+                        position = Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 1);
+                        velocity = 0f;
+                        autoScrollState.Complete();
+                    }
+                }
+                else
+                {
+                    var alpha = Mathf.Clamp01((Time.unscaledTime - autoScrollState.StartTime) /
+                                               Mathf.Max(autoScrollState.Duration, float.Epsilon));
+                    position = Mathf.LerpUnclamped(scrollStartPosition, autoScrollState.EndPosition,
+                        autoScrollState.EasingFunction(alpha));
+
+                    if (Mathf.Approximately(alpha, 1f))
+                    {
+                        autoScrollState.Complete();
+                    }
+                }
+
+                UpdatePosition(position);
+            }
+            else if (!(dragging || scrolling) && (!Mathf.Approximately(offset, 0f) || !Mathf.Approximately(velocity, 0f)))
+            {
+                var position = currentPosition;
+
+                if (movementType == MovementType.Elastic && !Mathf.Approximately(offset, 0f))
+                {
+                    autoScrollState.Reset();
+                    autoScrollState.Enable = true;
+                    autoScrollState.Elastic = true;
+
+                    UpdateSelection(Mathf.Clamp(Mathf.RoundToInt(position), 0, totalCount - 1));
+                }
+                else if (inertia)
+                {
+                    velocity *= Mathf.Pow(decelerationRate, deltaTime);
+
+                    if (Mathf.Abs(velocity) < 0.001f)
+                    {
+                        velocity = 0f;
+                    }
+
+                    position += velocity * deltaTime;
+
+                    if (snap.Enable && Mathf.Abs(velocity) < snap.VelocityThreshold)
+                    {
+                        ScrollTo(Mathf.RoundToInt(currentPosition), snap.Duration, snap.Easing);
+                    }
+                }
+                else
+                {
+                    velocity = 0f;
+                }
+
+                if (!Mathf.Approximately(velocity, 0f))
+                {
+                    if (movementType == MovementType.Clamped)
+                    {
+                        offset = CalculateOffset(position);
+                        position += offset;
+
+                        if (Mathf.Approximately(position, 0f) || Mathf.Approximately(position, totalCount - 1f))
+                        {
+                            velocity = 0f;
+                            UpdateSelection(Mathf.RoundToInt(position));
+                        }
+                    }
+
+                    UpdatePosition(position);
+                }
+            }
+
+            if (!autoScrollState.Enable && (dragging || scrolling) && inertia)
+            {
+                var newVelocity = (currentPosition - prevPosition) / deltaTime;
+                velocity = Mathf.Lerp(velocity, newVelocity, deltaTime * 10f);
+            }
+
+            prevPosition = currentPosition;
+            scrolling = false;
+        }
+
+        float CalculateMovementAmount(float sourcePosition, float destPosition)
+        {
+            if (movementType != MovementType.Unrestricted)
+            {
+                return Mathf.Clamp(destPosition, 0, totalCount - 1) - sourcePosition;
+            }
+
+            var amount = CircularPosition(destPosition, totalCount) - CircularPosition(sourcePosition, totalCount);
+
+            if (Mathf.Abs(amount) > totalCount * 0.5f)
+            {
+                amount = Mathf.Sign(-amount) * (totalCount - Mathf.Abs(amount));
+            }
+
+            return amount;
+        }
+
+        float CircularPosition(float p, int size) => size < 1 ? 0 : p < 0 ? size - 1 + (p + 1) % size : p % size;
+    }
+}

+ 12 - 0
Assets/BowArrow/FancyScrollView/Sources/Runtime/Scroller/Scroller.cs.meta

@@ -0,0 +1,12 @@
+fileFormatVersion: 2
+guid: 9f0e995f494626a4f878eedbded37c8d
+timeCreated: 1487408581
+licenseType: Free
+MonoImporter:
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Fonts.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d65c0dd0fb3c46244b143e540257a397
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 146 - 0
Assets/BowArrow/Fonts/CustomFontImportor.cs

@@ -0,0 +1,146 @@
+using UnityEngine;
+using System.Collections;
+using System.Collections.Generic;
+using System.Xml;
+using System;
+
+
+public class CustomFontImportor : MonoBehaviour
+{
+    public Font font;
+    public TextAsset textAsset;
+
+    //解析xml格式的fnt
+    // void Awake()
+    // {
+    //     if (font == null || textAsset == null)
+    //     {
+    //         //Debug.LogError("请设置font和textAsset.");
+    //         return;
+    //     }
+
+    //     XmlDocument xmlDocument = new XmlDocument();
+    //     xmlDocument.LoadXml(textAsset.text);
+
+
+    //     int totalWidth = Convert.ToInt32(xmlDocument["font"]["common"].Attributes["scaleW"].InnerText);
+    //     int totalHeight = Convert.ToInt32(xmlDocument["font"]["common"].Attributes["scaleH"].InnerText);
+
+    //     XmlElement xml = xmlDocument["font"]["chars"];
+    //     ArrayList characterInfoList = new ArrayList();
+
+
+    //     for (int i = 0; i < xml.ChildNodes.Count; ++i)
+    //     {
+    //         XmlNode node = xml.ChildNodes[i];
+    //         if (node.Attributes == null)
+    //         {
+    //             continue;
+    //         }
+    //         int index = Convert.ToInt32(node.Attributes["id"].InnerText);
+    //         int x = Convert.ToInt32(node.Attributes["x"].InnerText);
+    //         int y = Convert.ToInt32(node.Attributes["y"].InnerText);
+    //         int width = Convert.ToInt32(node.Attributes["width"].InnerText);
+    //         int height = Convert.ToInt32(node.Attributes["height"].InnerText);
+    //         int xOffset = Convert.ToInt32(node.Attributes["xoffset"].InnerText);
+    //         int yOffset = Convert.ToInt32(node.Attributes["yoffset"].InnerText);
+    //         int xAdvance = Convert.ToInt32(node.Attributes["xadvance"].InnerText);
+    //         CharacterInfo info = new CharacterInfo();
+    //         Rect uv = new Rect();
+    //         uv.x = (float)x / totalWidth;
+    //         uv.y = (float)(totalHeight - y - height) / totalHeight;
+    //         uv.width = (float)width / totalWidth;
+    //         uv.height = (float)height / totalHeight;
+    //         info.index = index;
+    //         info.uvBottomLeft = new Vector2(uv.xMin, uv.yMin);
+    //         info.uvBottomRight = new Vector2(uv.xMax, uv.yMin);
+    //         info.uvTopLeft = new Vector2(uv.xMin, uv.yMax);
+    //         info.uvTopRight = new Vector2(uv.xMax, uv.yMax);
+    //         info.minX = xOffset;
+    //         info.maxX = xOffset + width;
+    //         info.minY = -yOffset - height;
+    //         info.maxY = -yOffset;
+    //         info.advance = xAdvance;
+    //         info.glyphWidth = width;
+    //         info.glyphHeight = height;
+    //         characterInfoList.Add(info);
+    //     }
+    //     font.characterInfo = characterInfoList.ToArray(typeof(CharacterInfo)) as CharacterInfo[];
+    //     Debug.Log("生成成功.");
+    // }
+
+    //解析txt格式的fnt
+    void Start()
+    {
+        if (font == null || textAsset == null)
+        {
+            //Debug.LogError("请设置font和textAsset.");
+            return;
+        }
+
+        Hashtable kvs = new Hashtable();
+        List<Hashtable> chars = new List<Hashtable>();
+        string[] kvStrs = textAsset.text.Split(new char[]{' ', '\n'});
+        Hashtable charInfo = null;
+        int charListIndex = 0;
+        foreach (var kvStr in kvStrs)
+        {
+            if (kvStr.Trim().Length == 0) continue;
+            if (kvStr == "char") {
+                if (charListIndex != chars.Count) chars.Add(charInfo);
+                charInfo = new Hashtable();
+                charListIndex++;
+                continue;
+            }
+            if (!kvStr.Contains("=")) continue;
+            string k = kvStr.Split(new char[]{'='})[0];
+            string v = kvStr.Split(new char[]{'='})[1];
+            if (charInfo != null) {
+                charInfo.Add(k, v);
+                continue;
+            } 
+            kvs.Add(k, v);
+        }
+        if (charListIndex != chars.Count) chars.Add(charInfo);
+
+        int totalWidth = Convert.ToInt32(kvs["scaleW"]);
+        int totalHeight = Convert.ToInt32(kvs["scaleH"]);
+
+        ArrayList characterInfoList = new ArrayList();
+
+        for (int i = 0; i < chars.Count; ++i)
+        {
+            Hashtable charRecord = chars[i];
+            int index = Convert.ToInt32(charRecord["id"]);
+            int x = Convert.ToInt32(charRecord["x"]);
+            int y = Convert.ToInt32(charRecord["y"]);
+            int width = Convert.ToInt32(charRecord["width"]);
+            int height = Convert.ToInt32(charRecord["height"]);
+            int xOffset = Convert.ToInt32(charRecord["xoffset"]);
+            int yOffset = Convert.ToInt32(charRecord["yoffset"]);
+            int xAdvance = Convert.ToInt32(charRecord["xadvance"]);
+            CharacterInfo info = new CharacterInfo();
+            Rect uv = new Rect();
+            uv.x = (float)x / totalWidth;
+            uv.y = (float)(totalHeight - y - height) / totalHeight;
+            uv.width = (float)width / totalWidth;
+            uv.height = (float)height / totalHeight;
+            info.index = index;
+            info.uvBottomLeft = new Vector2(uv.xMin, uv.yMin);
+            info.uvBottomRight = new Vector2(uv.xMax, uv.yMin);
+            info.uvTopLeft = new Vector2(uv.xMin, uv.yMax);
+            info.uvTopRight = new Vector2(uv.xMax, uv.yMax);
+            info.minX = xOffset;
+            info.maxX = xOffset + width;
+            info.minY = -yOffset - height;
+            info.maxY = -yOffset;
+            info.advance = xAdvance;
+            info.glyphWidth = width;
+            info.glyphHeight = height;
+            characterInfoList.Add(info);
+        }
+        font.characterInfo = characterInfoList.ToArray(typeof(CharacterInfo)) as CharacterInfo[];
+        Debug.Log("生成成功.");
+    }
+}
+

+ 11 - 0
Assets/BowArrow/Fonts/CustomFontImportor.cs.meta

@@ -0,0 +1,11 @@
+fileFormatVersion: 2
+guid: bb4e4aadbd656cd4098bc4f94d4efcaf
+MonoImporter:
+  externalObjects: {}
+  serializedVersion: 2
+  defaultReferences: []
+  executionOrder: 0
+  icon: {instanceID: 0}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Fonts/HarmonyOS_Sans_SC_Regular.ttf


+ 21 - 0
Assets/BowArrow/Fonts/HarmonyOS_Sans_SC_Regular.ttf.meta

@@ -0,0 +1,21 @@
+fileFormatVersion: 2
+guid: 6b6cc7ab59ef00947950b61fdca2d042
+TrueTypeFontImporter:
+  externalObjects: {}
+  serializedVersion: 4
+  fontSize: 16
+  forceTextureCase: -2
+  characterSpacing: 0
+  characterPadding: 1
+  includeFontData: 1
+  fontNames:
+  - HarmonyOS Sans SC
+  fallbackFontReferences: []
+  customCharacters: 
+  fontRenderingMode: 0
+  ascentCalculationMode: 1
+  useLegacyBoundsCalculation: 0
+  shouldRoundAdvanceValue: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Fonts/MySDF.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 76cc08af59265034fb6379042c4050f0
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

File diff suppressed because it is too large
+ 1095 - 0
Assets/BowArrow/Fonts/MySDF/Billboard.asset


+ 8 - 0
Assets/BowArrow/Fonts/MySDF/Billboard.asset.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 9691dddcdf0268648bb6e2dcf201f7bb
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 11400000
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Fonts/MySDF/TTC.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: e4b78d6cfe1ae7141af571da896bd77d
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Fonts/MySDF/TTC/msyh.ttc


+ 21 - 0
Assets/BowArrow/Fonts/MySDF/TTC/msyh.ttc.meta

@@ -0,0 +1,21 @@
+fileFormatVersion: 2
+guid: 60dc4c0d16e7d9b409b09053503d801a
+TrueTypeFontImporter:
+  externalObjects: {}
+  serializedVersion: 4
+  fontSize: 16
+  forceTextureCase: -2
+  characterSpacing: 0
+  characterPadding: 1
+  includeFontData: 1
+  fontNames:
+  - Microsoft YaHei
+  fallbackFontReferences: []
+  customCharacters: 
+  fontRenderingMode: 0
+  ascentCalculationMode: 1
+  useLegacyBoundsCalculation: 0
+  shouldRoundAdvanceValue: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Fonts/智能黑体.ttf


+ 21 - 0
Assets/BowArrow/Fonts/智能黑体.ttf.meta

@@ -0,0 +1,21 @@
+fileFormatVersion: 2
+guid: 1ad2cf6c2f09744489d8c60b3fe3bab2
+TrueTypeFontImporter:
+  externalObjects: {}
+  serializedVersion: 4
+  fontSize: 16
+  forceTextureCase: -2
+  characterSpacing: 0
+  characterPadding: 1
+  includeFontData: 1
+  fontNames:
+  - AliHYAiHei
+  fallbackFontReferences: []
+  customCharacters: 
+  fontRenderingMode: 0
+  ascentCalculationMode: 1
+  useLegacyBoundsCalculation: 0
+  shouldRoundAdvanceValue: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Materials.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 602a639c2ea773e4ba91c0015afd27ca
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Materials/Color.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: a5787e3013dfea742b78c9da7acff347
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 79 - 0
Assets/BowArrow/Materials/Color/ColorGreen.mat

@@ -0,0 +1,79 @@
+%YAML 1.1
+%TAG !u! tag:unity3d.com,2011:
+--- !u!21 &2100000
+Material:
+  serializedVersion: 6
+  m_ObjectHideFlags: 0
+  m_CorrespondingSourceObject: {fileID: 0}
+  m_PrefabInstance: {fileID: 0}
+  m_PrefabAsset: {fileID: 0}
+  m_Name: ColorGreen
+  m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
+  m_ShaderKeywords: 
+  m_LightmapFlags: 4
+  m_EnableInstancingVariants: 0
+  m_DoubleSidedGI: 0
+  m_CustomRenderQueue: -1
+  stringTagMap: {}
+  disabledShaderPasses: []
+  m_SavedProperties:
+    serializedVersion: 3
+    m_TexEnvs:
+    - _BumpMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailAlbedoMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailMask:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _DetailNormalMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _EmissionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MainTex:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _MetallicGlossMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _OcclusionMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    - _ParallaxMap:
+        m_Texture: {fileID: 0}
+        m_Scale: {x: 1, y: 1}
+        m_Offset: {x: 0, y: 0}
+    m_Ints: []
+    m_Floats:
+    - _BumpScale: 1
+    - _Cutoff: 0.5
+    - _DetailNormalMapScale: 1
+    - _DstBlend: 0
+    - _GlossMapScale: 1
+    - _Glossiness: 0.5
+    - _GlossyReflections: 1
+    - _Metallic: 0
+    - _Mode: 0
+    - _OcclusionStrength: 1
+    - _Parallax: 0.02
+    - _SmoothnessTextureChannel: 0
+    - _SpecularHighlights: 1
+    - _SrcBlend: 1
+    - _UVSec: 0
+    - _ZWrite: 1
+    m_Colors:
+    - _Color: {r: 0, g: 1, b: 0, a: 1}
+    - _EmissionColor: {r: 0, g: 0, b: 0, a: 1}
+  m_BuildTextureStacks: []

+ 8 - 0
Assets/BowArrow/Materials/Color/ColorGreen.mat.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7619d8a08321edc41b0b34188750278c
+NativeFormatImporter:
+  externalObjects: {}
+  mainObjectFileID: 2100000
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Models.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 6965610e5c67433409b2b8d11ed27894
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Modules.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: 7f8825cbaf149d941a122b94358bfb93
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Resources.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: d54b5f746e557ee4fad770cff9d1c94e
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Resources/Audios.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: c63fd5d1317ca0742bd7b98b81c14efe
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

+ 8 - 0
Assets/BowArrow/Resources/Audios/Animal.meta

@@ -0,0 +1,8 @@
+fileFormatVersion: 2
+guid: ebaaa8b949f299944bc89da267578873
+folderAsset: yes
+DefaultImporter:
+  externalObjects: {}
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/Animal/arrow_enter.mp3


+ 22 - 0
Assets/BowArrow/Resources/Audios/Animal/arrow_enter.mp3.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: 9bb7d64b61b48aa46b7e9357261592d0
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/Animal/bird_injured.mp3


+ 22 - 0
Assets/BowArrow/Resources/Audios/Animal/bird_injured.mp3.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: 63ab1b88114248f408d4e7b28bd87520
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/Animal/man_injured.wav


+ 22 - 0
Assets/BowArrow/Resources/Audios/Animal/man_injured.wav.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: b9b2683cc62cc814784eb58f53398f07
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/Animal/rabbit_injured.mp3


+ 22 - 0
Assets/BowArrow/Resources/Audios/Animal/rabbit_injured.mp3.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: 3ea18e6d5bd029b4caa2d7a5f78483a9
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/Animal/wolf_die.wav


+ 22 - 0
Assets/BowArrow/Resources/Audios/Animal/wolf_die.wav.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: ae3f6cc22b8846248b54af3f36fe39c6
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/Animal/wolf_injured.mp3


+ 22 - 0
Assets/BowArrow/Resources/Audios/Animal/wolf_injured.mp3.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: 98bb3013ee798454a87ac701c2715f98
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/btn.wav


+ 22 - 0
Assets/BowArrow/Resources/Audios/btn.wav.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: c6f7726ef16a54b4c812a3f5528012a8
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

BIN
Assets/BowArrow/Resources/Audios/hit.mp3


+ 22 - 0
Assets/BowArrow/Resources/Audios/hit.mp3.meta

@@ -0,0 +1,22 @@
+fileFormatVersion: 2
+guid: 04ba2d9286fc28d4088655ba10ae9e95
+AudioImporter:
+  externalObjects: {}
+  serializedVersion: 6
+  defaultSettings:
+    loadType: 0
+    sampleRateSetting: 0
+    sampleRateOverride: 44100
+    compressionFormat: 1
+    quality: 1
+    conversionMode: 0
+  platformSettingOverrides: {}
+  forceToMono: 0
+  normalize: 1
+  preloadAudioData: 1
+  loadInBackground: 0
+  ambisonic: 0
+  3D: 1
+  userData: 
+  assetBundleName: 
+  assetBundleVariant: 

Some files were not shown because too many files changed in this diff