Jorayen
Jorayen

Reputation: 1971

Curve and stretch a sprite to mouse click/touch

I wish to create a game object where you can start dragging it by touching somewhere on the line of its collision and to create these "dynamic shapes" by stretching the sprites and readjusting the sprite look and collision according to the drag point.

Adding an illustration for clearance: enter image description here

Started playing with Sprite Shape Renderer to create these curved sprite tiles but I need to be able to create dynamic ones using the mouse cursor and adjust all collisions.

I've tried to add an AnchorDragger script to the Sprite Shape Renderer object:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.U2D;

public class AnchorDragger : MonoBehaviour
{
    const int INVALLID_INSERTED_POINT_INDEX = -1;

    public SpriteShapeController spriteShapeController;
    private Spline spline;
    private int inseretedPointIndex = INVALLID_INSERTED_POINT_INDEX;

    void Start()
    {
        spline = spriteShapeController.spline;
        int pointCount = spline.GetPointCount();
        for (var i = 0; i < pointCount; i++)
        {
            Vector3 currentPointPos = spline.GetPosition(i);
            Debug.Log("Point " + i + " position: " + currentPointPos);
        }
    }

    // Update is called once per frame
    void Update()
    {
        if (inseretedPointIndex != INVALLID_INSERTED_POINT_INDEX)
        {
            spline = spriteShapeController.spline;
            spline.SetPosition(inseretedPointIndex, Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 1.0f)));
            spriteShapeController.BakeCollider();
        }
    }

    void OnMouseDown()
    {
        Debug.Log("Mouse Down Position:" + Input.mousePosition);
        Vector3 mouseDownPos = Camera.main.ScreenToWorldPoint(new Vector3(Input.mousePosition.x, Input.mousePosition.y, 1.0f));
        Debug.Log("World Position: " + mouseDownPos);
        spline = spriteShapeController.spline;
        int pointCount = spline.GetPointCount();
        int closestPointIndex = int.MaxValue;
        float minDistance = int.MaxValue;
        for (var i = 0; i < pointCount; i++)
        {
            Vector3 currentPointPos = spline.GetPosition(i);
            float distance = Vector3.Distance(currentPointPos, mouseDownPos);
            if (distance < minDistance)
            {
                minDistance = distance;
                closestPointIndex = i;
            }
        }
        spline.InsertPointAt(closestPointIndex, mouseDownPos);
        spline.SetTangentMode(closestPointIndex, ShapeTangentMode.Continuous);
        inseretedPointIndex = closestPointIndex;
        Debug.Log("Inserted point index: " + inseretedPointIndex);
    }

    void OnMouseUp()
    {
        Debug.Log("Mouse Up");
        spline = spriteShapeController.spline;
        spline.RemovePointAt(inseretedPointIndex);
        inseretedPointIndex = INVALLID_INSERTED_POINT_INDEX;
    }
}

Basically tried to figure the closest point on the spline where I've clicked and then inserting a new point and setting its position on Update to where the mouse is and delete the point on mouse up.
Right now I'm having a problem where the drag position is not correct for some reason.
Where I clicked, where the new point position is set: enter image description here

even when I tried to play with where I click and where I take my mouse to while dragging, not sure why, could use help!

Upvotes: 1

Views: 680

Answers (1)

Ruzihm
Ruzihm

Reputation: 20259

Camera.ScreenToWorldPoint isn't appropriate here because you don't already know how far away to check from the camera, which is needed for the z position. An incorrect z component would give the nearest point on the spline to some point that the mouse is aligned with, but not on the sprite, and would modify the spline at an unexpected position:

enter image description here

Instead, draw a ray from the camera and see where it intersects with the plane the sprite lives on, and use that world position.

In Update:

void Update()
{
    if (inseretedPointIndex != INVALLID_INSERTED_POINT_INDEX)
    {
        spline = spriteShapeController.spline;

        Ray r = Camera.main.ScreenPointToRay(Input.mousePosition); 
        Plane p = new Plane(Vector3.forward,spriteShapeController.spline.GetPosition(0)); 

        float d; 
        p.Raycast(r,out d); 
        spline.SetPosition(inseretedPointIndex, r.GetPoint(d));

        spriteShapeController.BakeCollider();
    }
}

In OnMouseDown:

void OnMouseDown()
{
    Debug.Log("Mouse Down Position:" + Input.mousePosition);

    Ray r = Camera.main.ScreenPointToRay(Input.mousePosition); 
    Plane p = new Plane(Vector3.forward, spriteShapeController.spline.GetPosition(0)); 

    float d; 
    p.Raycast(r,out d); 

    Vector3 mouseDownPos = r.GetPoint(d);
    Debug.Log("World Position: " + mouseDownPos);

Upvotes: 1

Related Questions