Bezier based ArrowEmitter - what´s wrong in this code?

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.

8419968–1113915–ArrowEmitterTest.unitypackage (14.3 KB)

You’re probably just going to have to debug it. Here’s how to start:

When in doubt, print it out!™

What is often happening in these cases is one of the following:

  • the code you think is executing is not actually executing at all
  • the code is executing far EARLIER or LATER than you think
  • the code is executing far LESS OFTEN than you think
  • the code is executing far MORE OFTEN than you think
  • the code is executing on another GameObject than you think it is
  • you’re getting an error or warning and you haven’t noticed it in the console window

To help gain more insight into your problem, I recommend liberally sprinkling Debug.Log() statements through your code to display information in realtime.

Doing this should help you answer these types of questions:

  • is this code even running? which parts are running? how often does it run? what order does it run in?
  • what are the values of the variables involved? Are they initialized? Are the values reasonable?
  • are you meeting ALL the requirements to receive callbacks such as triggers / colliders (review the documentation)

Knowing this information will help you reason about the behavior you are seeing.

You can also supply a second argument to Debug.Log() and when you click the message, it will highlight the object in scene, such as Debug.Log("Problem!",this);

If your problem would benefit from in-scene or in-game visualization, Debug.DrawRay() or Debug.DrawLine() can help you visualize things like rays (used in raycasting) or distances.

You can also call Debug.Break() to pause the Editor when certain interesting pieces of code run, and then study the scene manually, looking for all the parts, where they are, what scripts are on them, etc.

You can also call GameObject.CreatePrimitive() to emplace debug-marker-ish objects in the scene at runtime.

You could also just display various important quantities in UI Text elements to watch them change as you play the game.

If you are running a mobile device you can also view the console output. Google for how on your particular mobile target, such as this answer or iOS: https://discussions.unity.com/t/700551 or this answer for Android: https://discussions.unity.com/t/699654

Another useful approach is to temporarily strip out everything besides what is necessary to prove your issue. This can simplify and isolate compounding effects of other items in your scene or prefab.

Here’s an example of putting in a laser-focused Debug.Log() and how that can save you a TON of time wallowing around speculating what might be going wrong:

https://discussions.unity.com/t/839300/3

You must find a way to get the information you need in order to reason about what the problem is.

Yeah, that I did, I removed a lot of debug tests and code I put in place, that´s why I am saying that it seems to be working fine. But it is not, I just couldn´t figure the flaw in the code, the bezier algorithm seems ok, and the way the prefabs are drawn made sense by reading the code.

You can see I added some gizmos rendering as well.

Which is why I think maybe I am missing something with the camera setup or similar. That could be, but, I am not sure I tried few different things to no avail. Might be something stupid for experienced people, specially since this concept is not something new or anything.

Mostly, I am curious on finding the flaw in this particular code that it is telling me that it should work but it is not. Somehow.