Line Renderer Script breaks intermittently

Good Morning,

I’m building a decorative data visualizer, which uses parsed historical price data from a coingecko api as input to draw the historical price chart as mountain shapes using the line renderer.

Just gonna get out front and say I’m artist and building this with help from ai. Trying my best to learn and understand the code as I go but I know this post is a hot mess so apologies in advance. Sharing as much info that seems related as possible to help provide relevant context. Any information you share will help me learn and be a better programmer.

The project itself is in open alpha testing right now, with lots of random bugs probably related to the canvas and iframe among others (meaning it doesn’t really work on mobile browsers yet) but you can check it out for your self at: Early Days Demo – OrchidxMachina

You’re invited to comment on any bugs you find but I’ll try to keep the scope of this post to one particular problem I’m having:

Specifically, and occasionally, in both Play Mode in the Unity Engine and maybe 1 out of every 20 loading from a browser the line renderer displays the mountain shapes correctly except everything is shifted downwards on the y axis. Restarting play mode or refreshing the browser fixes the problem and the mountains return to their proper position within the camera frame.


I enter playback mode, everything that is instantiated (including gameObjects tied to the same API in another script) are too low in playback.


I then restart play mode (and change nothing else) and everything then works fine.

The only pattern I have seen is that it has happened twice in the Unity Engine now on two separate days first thing after opening the scene. (and then worked for a minute just now and then broke again, but sometimes will go hours without breaking in play mode or browser during testing, without any changes being made to that portion of the game, either way…but also seems to break in play mode 1 out of every 4 times while doing research for this post.)

I tried to make the mountains solid with a mesh but they didn’t fill in. This may be adding to the problem because I learned that, while in play mode, turning the line renderer on/off with checkbox in Inspector turns off a second instantiation of the mountains which sits already outside the frame, then turning mesh on/off turns on and off the mountains which sit inside the frame.


The grey lines are the mesh inside the frame and the orange mesh is the actual points from the line renderer. Rereading the code it is not clear to me why the mesh sits separate from the line renderer itself, nor why the mesh isn’t filled in solid with public material.

Two things to add, which might be unrelated but could be helpful too.

  1. Both gameObjects and Line Renderer are generating positions from parsed JSON data with a “floor” gameobject as reference to create the y positions. Regardless as to whether the mountains appear correctly in the frame the floor object is not moving.

  2. There is a third script which generates the live price data for the text, which does not share data with the other two scripts, and even though it’s not related to the Line Renderer when it is turned off the Line Renderer breaks and disappears

Here’s the script for the line renderer, partially generated with help from chat-gpt, and i replaced my private api with a generic api for the same data point.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;

public class KSM_Hist_Mountains : MonoBehaviour
{
    private string URL = "https://api.coingecko.com/api/v3/coins/kusama/market_chart?vs_currency=usd&days=31&interval=daily";
    public LineRenderer lineRenderer;
    private GameObject floorObject;
    public Material mountains;

    public float[] ksm_x; // Public x position for KSM historical data, represents time
    public float[] ksm_y; // Public y position for KSM historical data, represents price
    public float scalingFactor = .00000000005f; // Public scaling factor

    void Start()
    {
        // Find the floor object in the scene, floor object is used as a reference for positioning the line renderer
        floorObject = GameObject.FindGameObjectWithTag("floor");
        if (floorObject != null)
        {
            // Start the coroutine to fetch KSM historical data
            StartCoroutine(GetKSMHistoricalData());
        }
        else
        {
            // Log an error if the floor object is not found
            Debug.LogError("Floor object not found!");
        }
    }

    IEnumerator GetKSMHistoricalData()
    {
        using (UnityWebRequest request = UnityWebRequest.Get(URL))
        {
            yield return request.SendWebRequest();

            if (request.result == UnityWebRequest.Result.ConnectionError)
            {
                // Log an error if there is a connection error
                Debug.LogError(request.error);
            }
            else
            {
                // Get the JSON response
                string json = request.downloadHandler.text;
                Debug.Log(json);

                // Parse the JSON data
                SimpleJSON.JSONNode data = SimpleJSON.JSON.Parse(json);
                SimpleJSON.JSONArray pricesArray = data["prices"].AsArray;
                Debug.Log(pricesArray);

                // Define the spacing between points, this helps space the points after scaling factor is applied
                float spacing = 2f;

                // Initialize the arrays
                ksm_x = new float[pricesArray.Count + 2];
                ksm_y = new float[pricesArray.Count + 2];

                // Add the first and last points
                ksm_x[0] = floorObject.transform.position.x;
                ksm_y[0] = floorObject.transform.position.y;


                // Iterate through the prices array and populate the arrays
                for (int i = 0; i < pricesArray.Count; i++)
                {
                    // Get the x and y values from the prices array
                    ksm_x[i + 1] = pricesArray[i][0].AsFloat;
                    ksm_y[i + 1] = pricesArray[i][1].AsFloat;

                    // Calculate the position of the line point
                    Vector3 position = floorObject.transform.position + new Vector3((ksm_x[i + 1] * scalingFactor) + 38f + ((i + 1) * spacing), ksm_y[i + 1] - 20, 0f);

                    // Set the position of the line point
                    lineRenderer.positionCount = i + 2;
                    lineRenderer.SetPosition(i + 1, position);
                }

                // Add the new point at the end, creating a point to complete the illusion of mountains
                ksm_x[ksm_x.Length - 2] = 100f;
                ksm_y[ksm_y.Length - 2] = 0f;

                // Calculate the position of the new point
                Vector3 newPosition = floorObject.transform.position + new Vector3(200f, 0f, 0f);

  //the following code is what's generating inside the scene, for good or ill

                // Set the position of the new point
                lineRenderer.positionCount = ksm_x.Length;
                lineRenderer.SetPosition(ksm_x.Length - 2, newPosition);

                // Create a mesh to fill the loop of lineRenderer
                Mesh mesh = new Mesh();
                lineRenderer.BakeMesh(mesh);

                // Assign the mountains material to the mesh
                MeshRenderer meshRenderer = lineRenderer.gameObject.AddComponent<MeshRenderer>();
                meshRenderer.sharedMaterial = mountains;

                // Assign the mesh to the mesh filter
                MeshFilter meshFilter = lineRenderer.gameObject.AddComponent<MeshFilter>();
                meshFilter.mesh = mesh;
            }
        }
    }
}

I’m happy to share the others scripts but don’t know if ya’ll want to see all three or just the one relevant to the line renderer so I’ll hold off on that for now.

Worth adding, obviously, I’m basically a coding noob still.

My apologies if I’m sharing TMI, I know the code’s buggy so maybe the extra info helps. Feel free to comment on any or all of it or none at all.

Thanks,

b

On another note:

While researching a solution, I’ve been thinking about how the script uses a coroutine, and that, while I don’t think it’s the problem, it’s been brought to my attention that one of the possible problems is that the coroutine is executing out of order with the parsing of the data. Or maybe that is the problem…?

I don’t understand how that’s possible since it’s the coroutine that calls the data to begin with but either way, one thing I’m curious about in the broader context is that I plan to rewrite these scripts to use Unity’s Job System.

If I understand it right, it’s possible to still use coroutines with the Job System, but may not be most efficient since it forces the work onto the main thread.

With all that in mind, would it maybe be a better use of my/our time to, instead of debugging the current code till it’s stable, work to rewrite it for the Jobs system because solving this current problem would maybe just become irrelevant once I make those code changes?

Feel free to correct any assumptions I’m making that are wrong but realizing that the best long term solution might be less straightforward and more tangential than I previously realized.

I already linked you everything I know / keep about mesh generation in Unity:

https://discussions.unity.com/t/940982/8

There’s nothing you can’t do by following that.

And you are the person to prove or disprove this via debugging.

By debugging you can find out exactly what your program is doing so you can fix it.

Use the above techniques to get the information you need in order to reason about what the problem is.

You can also use Debug.Log() to find out if any of your code is even running. Don’t assume it is.

Once you understand what the problem is, you may begin to reason about a solution to the problem.

1 Like