[2D Sprite Shape] Procedurally generated sprite shape texture shifting

Hey all,

I am working on a 2D side scrolling endless runner and have run into some issues using Sprite Shape. I am currently using a open ended sprite shape with a script that inserts a new point to the right of the camera view every time the right camera edge gets close to the rightmost point, and removes old points that are off screen on the left.

However, I am running into an issue where the texture of the sprite shape is shifting, every time a new point is added, or old point removed. I was able to partly fix this by disabling “Adaptive UV”, which fixed any shifting when new points are added, however, whenever old points are removed, the texture still shifts.

Media1

Here is the script:

using UnityEngine;
using UnityEngine.U2D;

public class TerrainGenerator : MonoBehaviour
{
    public SpriteShapeController spriteShapeController; // Reference to the Sprite Shape Controller
    public Transform playerTransform; // Reference to the player character (followed by Cinemachine)
    public float terrainSpeed = 20f; // Speed at which the terrain moves
    public float maxYOffset = 2f; // Maximum offset for Y position of new points
    public float preGenerationDistance = 20f; // Offset before the rightmost point reaches the camera view to generate new points

    private float pointSpacing; // Spacing between spline points, calculated from initial setup
    private float nextUpdate = 0f; // Timer for controlling spline updates
    void Start()
    {
        if (spriteShapeController != null)
        {
            // Ensure we have at least two points to work with
            if (spriteShapeController.spline.GetPointCount() >= 2)
            {
                // Calculate the distance between the first and second points to use as point spacing
                pointSpacing = spriteShapeController.spline.GetPosition(1).x - spriteShapeController.spline.GetPosition(0).x;
            }
            else
            {
                Debug.LogError("SpriteShapeController must have at least two initial points set in the scene.");
            }
        }
    }

    void Update()
    {
        if (spriteShapeController == null || playerTransform == null) return;

        // Move the entire terrain to the left to simulate player movement
        MoveTerrain();

        // Run terrain updates at a fixed interval for performance efficiency
        if (Time.time > nextUpdate)
        {
            nextUpdate = Time.time + 0.1f; // Update every 0.1 seconds

            // Check if the second point reached the threshold (right side of the camera view with preemptive offset)
            float rightEdge = Camera.main.transform.position.x + Camera.main.orthographicSize * Camera.main.aspect + preGenerationDistance;
            Vector3 secondPointPosition = spriteShapeController.transform.TransformPoint(spriteShapeController.spline.GetPosition(spriteShapeController.spline.GetPointCount() - 1));
            if (secondPointPosition.x <= rightEdge)
            {
                AddNewPoint();
            }

            // Remove leftmost point if it's outside the camera view and not part of a segment containing the player or the first point to the left of the segment
            RemoveLeftmostPointIfNeeded();
            spriteShapeController.RefreshSpriteShape();
        }

    }

    void MoveTerrain()
    {
        // Move the entire Sprite Shape Controller GameObject to the left
        transform.position -= new Vector3(terrainSpeed * Time.deltaTime, 0, 0);
    }

    void AddNewPoint()
    {
        // Get the position of the current last point
        int lastPointIndex = spriteShapeController.spline.GetPointCount() - 1;
        Vector3 lastPointPosition = spriteShapeController.spline.GetPosition(lastPointIndex);

        // Calculate the new point's position relative to the last point, with a random Y offset
        float newXPosition = lastPointPosition.x + pointSpacing;
        float newYPosition = lastPointPosition.y + Random.Range(-maxYOffset, maxYOffset); // Add small deviations to Y position
        Vector3 newPointPosition = new Vector3(newXPosition, newYPosition, 0);

        // Insert new point at the end of the spline
        spriteShapeController.spline.InsertPointAt(lastPointIndex + 1, newPointPosition);
        spriteShapeController.spline.SetTangentMode(lastPointIndex + 1, ShapeTangentMode.Continuous);

        // spriteShapeController.BakeCollider();
    }

    void RemoveLeftmostPointIfNeeded()
    {
        if (spriteShapeController.spline.GetPointCount() <= 2) return;

        float leftEdge = Camera.main.transform.position.x - Camera.main.orthographicSize * Camera.main.aspect;
        Vector3 playerPosition = playerTransform.position;

        // Iterate through the spline points to find the segment the player is within
        int playerSegmentIndex = -1;
        for (int i = 0; i < spriteShapeController.spline.GetPointCount() - 1; i++)
        {
            Vector3 currentPoint = spriteShapeController.transform.TransformPoint(spriteShapeController.spline.GetPosition(i));
            Vector3 nextPoint = spriteShapeController.transform.TransformPoint(spriteShapeController.spline.GetPosition(i + 1));

            if (playerPosition.x >= currentPoint.x && playerPosition.x <= nextPoint.x)
            {
                playerSegmentIndex = i;
                break;
            }
        }

        // Remove points that are outside the camera view and not part of the segment containing the player or the first point to the left of that segment
        if (playerSegmentIndex > 0)
        {
            for (int i = 0; i < playerSegmentIndex - 1; i++)
            {
                Vector3 point = spriteShapeController.transform.TransformPoint(spriteShapeController.spline.GetPosition(0));
                if (point.x < leftEdge)
                {
                    spriteShapeController.spline.RemovePointAt(0);
                }
                else
                {
                    break;
                }
            }
        }
        // spriteShapeController.BakeCollider();
    }
}

Same problem as this guy but, the solution on his post doesn’t really apply to mine.
https://discussions.unity.com/t/sprite-shape-shifting-when-adding-removing-new-points-to-spline-during-runtime/761343

If any one has any ideas of how to solve this, or if this method of procedural generation is even valid, please let me know!

Thanks!

EDIT: I’ve considered switching to a repositioning the control points method and using a scrolling shader to show movement instead but, I want to exhaust this method first, since it is easier to implement.