I went looking everywhere for something to help with this and wound up spending the morning writing my own. Since this community has been so helpful to me, I decided I’d post some code that should help get someone started if they want to display curves in UI at runtime.
First you’ll need to create a “Plot Point” prefab. Mine is just a simple 1x1 UI image with a blue color. Then attach this script to a UI object that has a RectTransform. You’ll probably want to set the anchors on this target RectTransform to lower left, or you may end up doing some adjustment to this code.
// Plot Curve for Unity UI Display
//This is free and unencumbered software released into the public domain.
//Anyone is free to copy, modify, publish, use, compile, sell, or
//distribute this software, either in source code form or as a compiled
//binary, for any purpose, commercial or non-commercial, and by any
//means.
//In jurisdictions that recognize copyright laws, the author or authors
//of this software dedicate any and all copyright interest in the
//software to the public domain. We make this dedication for the benefit
//of the public at large and to the detriment of our heirs and
//successors. We intend this dedication to be an overt act of
//relinquishment in perpetuity of all present and future rights to this
//software under copyright law.
//THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
//EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
//MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
//IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
//OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
//ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
//OTHER DEALINGS IN THE SOFTWARE.
//For more information, please refer to <http://unlicense.org/>
using System.Collections.Generic;
using UnityEngine;
/// <summary>
/// Plots an approximation of a Unity AnimationCurve object for display in Unity UI
/// </summary>
public class PlotCurveUI : MonoBehaviour
{
public GameObject plotPointPrefab;
private List<GameObject> _plotPoints = new List<GameObject>();
private RectTransform _plotRect;
/// <summary>
/// Store the RectTransform so we don't retrieve it a million times.
/// </summary>
private void Start()
{
_plotRect = GetComponent<RectTransform>();
}
/// <summary>
/// Add plot point gameobjects to a rectTransform resulting in a plot you can show in Unity UI.
/// </summary>
/// <param name="curve">the animation curve you want to plot</param>
/// <param name="xPoints">optional: the quantity of datapoints you want to collect</param>
internal void BuildPlot(AnimationCurve curve, int xPoints = 40)
{
float[,] dataPoints = EvalAnimationCurve(curve, xPoints);
//NOTE: scales don't currently account for Unity UI scaling itself, which you may need to do.
float scaleY = _plotRect.rect.height - 1f;
float scaleX = _plotRect.rect.width / xPoints;
float plotYAdjust = scaleY + 0.5f;
for (int i = 0; i < dataPoints.GetLength(0); i++)
{
Vector3 plotPos = new Vector3(i * scaleX + 0.5f, //0.5 is half the width of my plotPoint prefab
dataPoints[i, 1] * scaleY - plotYAdjust, //pivot point of my plot rects are lower left. this would probably be different if pivot was different.
0f);
plotPos = _plotRect.TransformPoint(plotPos);
//reuse existing plot points if available, or create new.
//NOTE: If you wanted sparse graphs that look like line graphs, you could set the width of the prefab rectTransform
//and do some quaternion math using neighboring points to rotate the point to fit the slope.
//I didn't do this because it's not necessary for my purposes, and I don't feel right now like doing quaternion stuff.
if (i < _plotPoints.Count)
{
//print("reusing plot point");
_plotPoints[i].transform.position = plotPos;
}
else
{
GameObject newPlotPoint = Instantiate(plotPointPrefab, plotPos, Quaternion.identity, _plotRect);
_plotPoints.Add(newPlotPoint);
}
}
}
/// <summary>
/// Evaluate an animation curve to get input and output at given intervals.
/// </summary>
/// <param name="curve">The animation curve to be evaluated.</param>
/// <param name="dataPoints">The number of data points you want collected.</param>
/// <returns></returns>
private static float[,] EvalAnimationCurve(AnimationCurve curve, int dataPoints)
{
var firstKey = curve.keys[0];
var lastKey = curve.keys[curve.keys.Length - 1];
//float evalInterval = (lastKey.time - firstKey.time) / (dataPoints + 1);
float evalInterval = (lastKey.time - firstKey.time) / dataPoints;
float timeEval = firstKey.time;
float[,] evalPairs = new float[dataPoints, 2];
for (int i = 0; i < dataPoints; i++)
{
evalPairs[i, 0] = timeEval;
evalPairs[i, 1] = curve.Evaluate(timeEval);
timeEval += evalInterval;
}
return evalPairs;
}
}
Plots can then be built in your target RectTransform by:
int dataPoints = 40 (or however many data points you want on your plot)
AnimationCurve animCurve = [transform with your AnimationCurve].GetComponent<AnimationCurve>();
PlotCurveUI plot = [your plot RectTransform].GetComponent<PlotCurveUI>();
plot.BuildPlot(animCurve, dataPoints);
There are many ways that I can think of to extend or improve this, and it’s pretty much guaranteed imperfect, but as it sits it serves my needs and seems reasonably efficient.
Obvious weak points are: (more on these in the code comments)
-Does not include curve editing of any sort, though it could be added.
-Uses a GameObject for each point. This is good if you want to extend functionality, bad if you want to plot a lot of points, and totally impractical if you want to plot thousands of points.
-Not tested on very large plots.
-Probably not suitable for very sparse data points.
Strong points are:
-Free to use however you’d like. License is included in the script.
-Reuses plot point GameObjects and resets position if the plot is rebuilt.
-Scales pretty well (I think) to the size of your RectTransform.
-Very easy to use. Once setup is done, just call BuildPlot(your AnimationCurve, and optionally, the number of data points you need evaluated).
Hope it helps someone!
[edit] I forgot to mention that all of the Y values of my animation curves are between 1 and 0. This is important because you’ll need to add some additional code for scaling if this isn’t the case for you. You’ll get some insane results if you plug in a curve with a y value above 1 or below zero. I’ll post an edit if I need to tackle this situation, provided people are using the script ![]()