1

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!

Jorayen
  • 1,737
  • 2
  • 21
  • 52
  • Ooh, this question got really good with these last edits. Great work. Are you using a perspective camera or an orthographic one? – Ruzihm Oct 18 '19 at 12:37
  • I'm using perspective camera. Any ideas on why the transformed mouse input to world coordinates is not correct? – Jorayen Oct 18 '19 at 13:28
  • I have an idea. Try getting rid of this line `Vector3 mouseDownPos = ...` and replacing it with `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);`. If that fixes it let me know and I'll write up an answer explaining more – Ruzihm Oct 18 '19 at 13:35
  • Still not working as expected. Here's an image showing where I clicked and where the point was set to: https://imgur.com/a/NQ4NjEh – Jorayen Oct 18 '19 at 13:46
  • It seems to be trying to adjust the spline at the correct point in the spline, it's just moving it to the wrong position. Try also getting rid of `spline.SetPosition(...` and replacing it with `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));` – Ruzihm Oct 18 '19 at 13:53
  • @Ruzihm ok now it seems to work fine. Could you explain the difference as I'm completely beginner to unity and game development in general. Also I was wondering if you could give me direction on how to implement this stretching mechanic as a "rubber band" so when I stop dragging the mouse the ground will shoot back up and not just "reposition" itself to the original state by deleting the inserted point. Thanks ! – Jorayen Oct 18 '19 at 14:12
  • Oh I see there're some edge cases where I click on one point and it drags it from somewhere else: https://imgur.com/a/SE05pnr And when clicking the most left side: https://imgur.com/a/2cjK0QJ – Jorayen Oct 18 '19 at 14:13

1 Answers1

1

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);
Ruzihm
  • 19,749
  • 5
  • 36
  • 48
  • What do you mean I don't know how far to check from camera? Could you explain it visually maybe? Also can you refer to the messages I've posted on my OP seeing some edge cases regarding this method. – Jorayen Oct 18 '19 at 14:45
  • What do you mean I don't know how far to check from camera? - you previously used a hardcoded `1.0f` but there's not reason to think that's how far you're interested in going away from the camera (which is what the z component does in `ScreenToWorldPoint`) – Ruzihm Oct 18 '19 at 14:52
  • As for how to handle the edge case, that would probably be best asked as an additional question in order to avoid this question becoming a [chameleon question](https://meta.stackexchange.com/questions/43478/exit-strategies-for-chameleon-questions). – Ruzihm Oct 18 '19 at 14:53
  • but how does the z component affects the transformation such that when I click on the screen it transform it to somewhere completely else? – Jorayen Oct 18 '19 at 15:18
  • @Jorayen I added some more detail to the explanation and a diagram to illustrate an example. – Ruzihm Oct 18 '19 at 15:32
  • So if I'm understanding it correctly, your solution project a plane on the sprite and finding the intersection of a ray casted from the mouse into space with this play thus guaranteeing a correct point on the sprite since the plane sits on it. Wouldn't it be possible then to use dynamic z like so: `Camera.main.transform.position.z`. – Jorayen Oct 18 '19 at 15:43
  • The first part: yes, that's the idea. Second part: for starters, `Camera.main.transform.position.z` has nothing to do with the position of the mouse so it won't be valid for all mouse positions, as the distance from the camera to the point of the sprite "under" the mouse varies. – Ruzihm Oct 18 '19 at 16:04