I am trying to do the typical arrow in TCG (card games) such as Slay The Spire, Monster Train and many others. After some reading I figured the technique uses a Bezier curve, in particular a cubic Bezier curve.
So, I found a video tutorial that also explains that and its author was showing some code, this is the video url at the code mark time which is what matters at this point:
So that this post is more readable:
Then I decided to copy everything that the guy put there on the screen recorded, when I went to test it… it does not work. At all. I was reading the code and reviewing things again and again… and It kind of feels like it should work but the arrow either does not appear or appears totally wrong.
At this point I don´t know what´s wrong with the code so I thought I would ask here in case someone could guide me to what I am doing wrong on my setup for the code not to work as expected. I can´t quite see why it would show up like inverted.
I added a sample scene with to hexagon to assist, in case I am doing anything wrong setting up my camera or something.
You can see in the image how the arrow head and segments appear below and the actual control points I am drawing them using gizmos, then in the canvas I placed to hexagons to control things a bit better.
Below is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Arrow.Bezier
{
public class Arrow : MonoBehaviour
{
#region arrow prefabs objects
public GameObject HeadPrefab; // arrowHeadPrefab in the video
public GameObject SegmentPrefab; // arrowNodePrefab in the video
public int segmentsNum = 10; //arrowNodeNum in the video
public float scaleFactor = 1f;
#endregion
#region RectTransforms and positions
private RectTransform origin;
private List<RectTransform> arrowNodes = new List<RectTransform>();
private List<Vector2> controlPoints = new List<Vector2>();
private readonly List<Vector2> controlPointFactors = new List<Vector2> { new Vector2(-0.3f, 0.8f), new Vector2(0.1f, 1.4f) };
#endregion
#region runtime methods
public void Awake()
{
this.origin = this.GetComponent<RectTransform>();
for (int i = 0; i < this.segmentsNum; i++)
{
this.arrowNodes.Add(Instantiate(this.SegmentPrefab, this.transform).GetComponent<RectTransform>());
}
this.arrowNodes.Add(Instantiate(this.HeadPrefab, this.transform).GetComponent<RectTransform>());
this.arrowNodes.ForEach(a => a.GetComponent<RectTransform>().position = new Vector2(-1000, -1000));
for (int i = 0; i < 4; ++i)
{
this.controlPoints.Add(Vector2.zero);
}
}
public void Update()
{
// P0 origin (source object)
this.controlPoints[0] = new Vector2(this.origin.position.x, this.origin.position.y);
// P3 destination (mouse)
// video does something else, the line below commented is custom code
// this.controlPoints[3] = new Vector3(Input.mousePosition.x, Input.mousePosition.y, Camera.main.nearClipPlane);
this.controlPoints[3] = new Vector2(Input.mousePosition.x, Input.mousePosition.y);
// Determine control points with formula
// p1 = P0 + (P3-P0) * Vector2
// p2 = P0 + (P3-P0) * Vector2
this.controlPoints[1] = this.controlPoints[0] + (this.controlPoints[3] - this.controlPoints[0]) * this.controlPointFactors[0];
this.controlPoints[2] = this.controlPoints[0] + (this.controlPoints[3] - this.controlPoints[0]) * this.controlPointFactors[1];
// debug lines not in the video
Debug.DrawLine(this.controlPoints[0], this.controlPoints[1], Color.red);
Debug.DrawLine(this.controlPoints[1], this.controlPoints[2], Color.blue);
Debug.DrawLine(this.controlPoints[2], this.controlPoints[3], Color.green);
for (int i = 1; i < this.arrowNodes.Count; ++i)
{
var t = Mathf.Log(1f * i / this.arrowNodes.Count - 1 + 1f, 2f);
// cubic Bezier curve
Vector2 pos = Mathf.Pow(1 - t, 3) * this.controlPoints[0] +
3 * Mathf.Pow(1 - t, 2) * t * this.controlPoints[1] +
3 * (1 - t) * Mathf.Pow(t, 2) * this.controlPoints[2] +
Mathf.Pow(t, 3) * this.controlPoints[3];
//Debug.DrawLine(new Vector2(Input.mousePosition.x, Input.mousePosition.y), this.arrowNodes[18].position, Color.cyan);
this.arrowNodes[i].position = pos;
if (i > 0)
{
var euler = new Vector3(0, 0, Vector2.SignedAngle(Vector2.up, this.arrowNodes[i].position - this.arrowNodes[i - 1].position));
this.arrowNodes[i].rotation = Quaternion.Euler(euler);
}
var scale = this.scaleFactor * (1f - 0.03f * (this.arrowNodes.Count - 1 - i));
this.arrowNodes[i].localScale = new Vector3(scale, scale, 1f);
}
this.arrowNodes[0].transform.rotation = this.arrowNodes[1].transform.rotation;
}
#endregion
}
}
I am also attaching a sample project in Unity, should you want to help here. Thanks in advance, any pointers will be more than welcome.