Unityでグラフを書く(折れ線偏)
今回はUnityでグラフをつくります(折れ線グラフ)
いろいろ探した結果,有料のグラフがでできたのですが
それは違うと思い、無料でグラフを作る方法を調べました
1日もかからずできます。
unityでグラフを書こうとしている人は参考に!
この動画を参考にしました
Unity Tutorial - Create a Graph
それを参考にして作ったグラフがこちら
それが組み込まれたアプリケーション play.google.com
普段わからない顔の大きさをAIを使って数値化するアプリです。日々顔の大きさを記録していき、ダイエットのモチベーションを高めます。
プログラムの全貌
using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using CodeMonkey.Utils; using System; using System.Linq; public class Window_Graph : MonoBehaviour { [SerializeField] private Sprite circleSprite; private RectTransform graphContainer; private RectTransform labelTemplateX; private RectTransform labelTemplateY; private RectTransform dashTemplateX; private RectTransform dashTemplateY; public Vector3 start; public Vector3 end; public float deltaX = 0f; private void Awake() { graphContainer = transform.Find("graphContainer").GetComponent<RectTransform>(); labelTemplateX = graphContainer.Find("labelTemplateX").GetComponent<RectTransform>(); labelTemplateY = graphContainer.Find("labelTemplateY").GetComponent<RectTransform>(); dashTemplateX = graphContainer.Find("dashTemplateX").GetComponent<RectTransform>(); dashTemplateY = graphContainer.Find("dashTemplateY").GetComponent<RectTransform>(); // スコアのロード var score_num = PlayerPrefs.GetInt("SCORE", 0); List<float> face_num = new List<float>(); for (int i = 0; i < 30; i++) { var data = (PlayerPrefs.GetFloat("Face" + i, 0)); if (data > 150) { face_num.Add(data); } } ShowGraph(face_num, (int _i) => "Day" + (_i + 1), (float _f) => Mathf.RoundToInt(_f)+"cm^2") ; } private GameObject CreateCircle(Vector2 anchoredPosition) { GameObject gameObject = new GameObject("circle", typeof(Image)); gameObject.transform.SetParent(graphContainer, false); gameObject.GetComponent<Image>().sprite = circleSprite; RectTransform rectTransform = gameObject.GetComponent<RectTransform>(); rectTransform.anchoredPosition = anchoredPosition; rectTransform.sizeDelta = new Vector2(11, 11); rectTransform.anchorMin = new Vector2(0, 0); rectTransform.anchorMax = new Vector2(0, 0); return gameObject; } private void ShowGraph(List<float> valueList, Func<int, string> getAxisLabelX=null, Func<float, string> getAxisLabelY=null) { if (getAxisLabelX == null) { getAxisLabelX = delegate (int _i) { return _i.ToString(); }; } if (getAxisLabelY == null) { getAxisLabelY = delegate (float _f) { return Mathf.RoundToInt(_f).ToString(); }; } float graphHeight = graphContainer.sizeDelta.y; float graphWidth = graphContainer.sizeDelta.x; float yMinimum = valueList[0]; float yMaximum = valueList[0]; foreach (int value in valueList) { if (value > yMaximum) { yMaximum = value; } if (value < yMinimum) { yMinimum = value; } } yMaximum = yMaximum + ((yMaximum-yMinimum)*0.2f); yMinimum = yMinimum - ((yMaximum-yMinimum)* 0.2f); float xSize = graphWidth/15; GameObject lastCircleGameObject = null; for (int i = 0; i<valueList.Count; i++) { float xPosition = xSize+ i * xSize; float yPosition = ((valueList[i]-yMinimum) / (yMaximum-yMinimum)) * graphHeight; GameObject circleGameObject = CreateCircle((new Vector2(xPosition, yPosition))); if(lastCircleGameObject != null) { CreateDotConnection(lastCircleGameObject.GetComponent<RectTransform>().anchoredPosition, circleGameObject.GetComponent<RectTransform>().anchoredPosition); } lastCircleGameObject = circleGameObject; RectTransform labelX = Instantiate(labelTemplateX); labelX.SetParent(graphContainer,false); labelX.gameObject.SetActive(true); labelX.anchoredPosition = new Vector2(xPosition, -7f); labelX.GetComponent<Text>().text = getAxisLabelX(i); RectTransform dashX = Instantiate(dashTemplateY); dashX.SetParent(graphContainer, false); dashX.gameObject.SetActive(true); dashX.anchoredPosition = new Vector2(xPosition, -7f); } int separatorCount = 10; for (int i = 0; i <= separatorCount; i++) { RectTransform labelY = Instantiate(labelTemplateY); labelY.SetParent(graphContainer, false); labelY.gameObject.SetActive(true); float normalizedValue = i * 1f / separatorCount; labelY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight); labelY.GetComponent<Text>().text = getAxisLabelY(yMinimum+( normalizedValue * (yMaximum-yMinimum))); RectTransform dashY = Instantiate(dashTemplateX); dashY.SetParent(graphContainer, false); dashY.gameObject.SetActive(true); dashY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight); } } private void CreateDotConnection(Vector2 dotPositionA, Vector2 dotPostionB) { GameObject gameObject = new GameObject("dotConnection", typeof(Image)); gameObject.transform.SetParent(graphContainer, false); gameObject.GetComponent<Image>().color = new Color(0, 1, 0, 1f); RectTransform rectTransform = gameObject.GetComponent<RectTransform>(); Vector2 dir = (dotPostionB - dotPositionA).normalized; float distance = Vector2.Distance(dotPositionA, dotPostionB); rectTransform.anchorMin = new Vector2(0, 0); rectTransform.anchorMax = new Vector2(0, 0); rectTransform.sizeDelta = new Vector2(distance, 3f); rectTransform.anchoredPosition = dotPositionA +dir*distance*0.5f ; rectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVector(dir)); } // Start is called before the first frame update void Start() { } // Update is called once per frame void Update() { } }
解説
配置したgraphContainerの中に作ったlabelと破線を取得します
graphContainer = transform.Find("graphContainer").GetComponent<RectTransform>(); labelTemplateX = graphContainer.Find("labelTemplateX").GetComponent<RectTransform>(); labelTemplateY = graphContainer.Find("labelTemplateY").GetComponent<RectTransform>(); dashTemplateX = graphContainer.Find("dashTemplateX").GetComponent<RectTransform>(); dashTemplateY = graphContainer.Find("dashTemplateY").GetComponent<RectTransform>();
PlayerPrefsでスマホ内部に保存したデータを1つずつ読み リストに追加します。 そのリストをshowgraph関数に渡すことによりグラフを描画します
showgraphの引数
(int i) => "Day" + (i + 1)は
Day1,Day2みたいにxのラベルを決める部分です
// スコアのロード var score_num = PlayerPrefs.GetInt("SCORE", 0); List<float> face_num = new List<float>(); for (int i = 0; i < 30; i++) { var data = (PlayerPrefs.GetFloat("Face" + i, 0)); if (data > 150) { face_num.Add(data); } } ShowGraph(face_num, (int _i) => "Day" + (_i + 1), (float _f) => Mathf.RoundToInt(_f)+"cm^2") ;
s
GameObjectを作るスクリプト
GameObject gameObject = new GameObject("circle", typeof(Image));
とすることで、新しくGameObjectを作るのですが、component(今回の場合はImage)をアタッチできます
AddComponent
作ったGameObjectの親をgraphContainerとしますが、
この時は親の位置や状態を気にせずローカル座標系で考えます?
gameObject.transform.SetParent(graphContainer, false);
private GameObject CreateCircle(Vector2 anchoredPosition) { GameObject gameObject = new GameObject("circle", typeof(Image)); gameObject.transform.SetParent(graphContainer, false); gameObject.GetComponent<Image>().sprite = circleSprite; RectTransform rectTransform = gameObject.GetComponent<RectTransform>(); rectTransform.anchoredPosition = anchoredPosition; rectTransform.sizeDelta = new Vector2(11, 11); rectTransform.anchorMin = new Vector2(0, 0); rectTransform.anchorMax = new Vector2(0, 0); return gameObject; }
一番需要なグラフをかく関数
Func<代入する型, 戻り値の型> func = (引数) => { 実行したいメソッド };
引数に関数の戻り値を指定してある ShowGraph(face_num, (int i) => "Day" + (i + 1), (float f) => Mathf.RoundToInt(f)+"cm2") ;
ymax,yminの2割の場所をグラフの描写エリアとします。
private void ShowGraph(List<float> valueList, Func<int, string> getAxisLabelX=null, Func<float, string> getAxisLabelY=null) { if (getAxisLabelX == null) { getAxisLabelX = delegate (int _i) { return _i.ToString(); }; } if (getAxisLabelY == null) { getAxisLabelY = delegate (float _f) { return Mathf.RoundToInt(_f).ToString(); }; } float graphHeight = graphContainer.sizeDelta.y; float graphWidth = graphContainer.sizeDelta.x; float yMinimum = valueList[0]; float yMaximum = valueList[0]; foreach (int value in valueList) { if (value > yMaximum) { yMaximum = value; } if (value < yMinimum) { yMinimum = value; } } yMaximum = yMaximum + ((yMaximum-yMinimum)*0.2f); yMinimum = yMinimum - ((yMaximum-yMinimum)* 0.2f); float xSize = graphWidth/15; GameObject lastCircleGameObject = null; for (int i = 0; i<valueList.Count; i++) { float xPosition = xSize+ i * xSize; float yPosition = ((valueList[i]-yMinimum) / (yMaximum-yMinimum)) * graphHeight; GameObject circleGameObject = CreateCircle((new Vector2(xPosition, yPosition))); if(lastCircleGameObject != null) { CreateDotConnection(lastCircleGameObject.GetComponent<RectTransform>().anchoredPosition, circleGameObject.GetComponent<RectTransform>().anchoredPosition); } lastCircleGameObject = circleGameObject; RectTransform labelX = Instantiate(labelTemplateX); labelX.SetParent(graphContainer,false); labelX.gameObject.SetActive(true); labelX.anchoredPosition = new Vector2(xPosition, -7f); labelX.GetComponent<Text>().text = getAxisLabelX(i); RectTransform dashX = Instantiate(dashTemplateY); dashX.SetParent(graphContainer, false); dashX.gameObject.SetActive(true); dashX.anchoredPosition = new Vector2(xPosition, -7f); }
y方向のラベルを何個表示するか
int separatorCount = 10; for (int i = 0; i <= separatorCount; i++) { RectTransform labelY = Instantiate(labelTemplateY); labelY.SetParent(graphContainer, false); labelY.gameObject.SetActive(true); float normalizedValue = i * 1f / separatorCount; labelY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight); labelY.GetComponent<Text>().text = getAxisLabelY(yMinimum+( normalizedValue * (yMaximum-yMinimum))); RectTransform dashY = Instantiate(dashTemplateX); dashY.SetParent(graphContainer, false); dashY.gameObject.SetActive(true); dashY.anchoredPosition = new Vector2(-7f, normalizedValue * graphHeight); }
ここで点を結びます UtilsClass.GetAngleFromVector(dir)を使うので、インポートしなければいけません。 これは点と点との角度を表しているものだと思っています。
private void CreateDotConnection(Vector2 dotPositionA, Vector2 dotPostionB) { GameObject gameObject = new GameObject("dotConnection", typeof(Image)); gameObject.transform.SetParent(graphContainer, false); gameObject.GetComponent<Image>().color = new Color(0, 1, 0, 1f); RectTransform rectTransform = gameObject.GetComponent<RectTransform>(); Vector2 dir = (dotPostionB - dotPositionA).normalized; float distance = Vector2.Distance(dotPositionA, dotPostionB); rectTransform.anchorMin = new Vector2(0, 0); rectTransform.anchorMax = new Vector2(0, 0); rectTransform.sizeDelta = new Vector2(distance, 3f); rectTransform.anchoredPosition = dotPositionA +dir*distance*0.5f ; rectTransform.localEulerAngles = new Vector3(0, 0, UtilsClass.GetAngleFromVector(dir)); }