4
\$\begingroup\$

enter image description here

I understand this is probably not an easy task considering I haven't found any code examples online that do something like this but if I wanted to take in mouse input lets say during a drag and use that input as soon as the user lets go of the mouse to calculate into some kind of slicing for a part of a mesh, what would I need to watch out for and what would be the best algorithms and data structures for this?

\$\endgroup\$
3
\$\begingroup\$

In theory this isn't that complicated -- given a point a somewhere on the surface of the circle, project a ray from the center of the circle through that point of length r. The endpoint of that line is where you slice the outer edge of the circle. Repeat for the other edge of the pie slice.

Vector2 calcEdgePoint(Vector2 mouseDownPosition,Vector2 circleCenter, float circleRadius)
   {
    Ray2D ray = new Ray2D(circleCenter, mouseDownPosition - circleCenter);

    return ray.GetPoint(circleRadius);
   }

diagram

How you put this in practice in your Unity game depends on how accurate you need to be and how exactly you want to handle user input.

Mesh MouseOver

A simple approach and what I would probably try first is to "pre-slice" the pizza (circle) mesh and simply track which portions the user drags over to determine which portions to subtract.

Here's a quick and dirty example.

  1. The meshes -- I used 3D to make sure convex colliders work, but you could probably get this to work with simple triangles. My pizza has 32 slices. Use as many as you need to get the desired resolution.

pizza mesh

  1. Scene set up -- "pie" parent with child meshes and a controller. The camera points straight down on pie.

heirarchy

  1. Each slice has a convex collider and a PieSlice script:

pie slice in inspector

  1. The pie slice script "phones home" to the PizzaPieSlicer controller with mouse enter, exit and (optionally) stay info:
using UnityEngine;

public class PieSlice : MonoBehaviour
{
    MeshRenderer mr;

    void Start()
    {
        mr = GetComponent<MeshRenderer>();
    }

    private void OnMouseEnter()
    {
        Debug.Log("entered");
        PizzaPieSlicer.instance.SliceEnter(mr);
    }

    private void OnMouseExit()
    {
        PizzaPieSlicer.instance.SliceExit(mr);
    }
}

The controller script tracks whether the pizza is "active" with a state machine. When no slices are active, it goes ahead and slices the pie.

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

public class PizzaPieSlicer : MonoBehaviour
{

    [SerializeField] Transform pieParent;
    [SerializeField] Material[] materials;
    public static PizzaPieSlicer instance;
   

    public enum State
    {
        slicing,
        idle,
        sliced
    }

    public List<Transform> slices = new List<Transform>(); // public for debugging
    public State state; // public for debugging;
    public int hit = 0; // maybe useful to count? public for debugging " "

    void Start()
    {
        instance = this;
        state = State.idle;

    }

    public void SliceEnter(MeshRenderer mr)
    {
        mr.sharedMaterial = materials[1];
        state = State.slicing;
        if (!slices.Contains(mr.transform))
        {
            slices.Add(mr.transform);
        }
        hit++;
    }

    public void SliceExit(MeshRenderer mr)
    {
        mr.sharedMaterial = materials[2];
        state = State.idle;
    }

    public void SliceStay()
    {
        state = State.slicing;
    }

    void SliceIt()
    {
        var sliceParent = new GameObject().GetComponent<Transform>();

        foreach(Transform t in slices)
        {
            t.SetParent(sliceParent);
        }

        sliceParent.position = new Vector3(1, 1, 1); //Do something more intelligent here
    }

    void Update()
    {
        switch (state)
        {
            case State.idle:
                if(hit > 0)
                {
                    Debug.Log("Slice it!");
                    SliceIt();
                    state = State.sliced;
                }
                break;

            case State.slicing:
                // do something 
                break;

            case State.sliced:
                // reset maybe?
                break;

            default:
                break;
        }
    }
}

Demo:

Demo

Note: I had intended for the PieSlice script to notify PizzaPieSlicer during OnMouseOver events, but it turns out this may be unnecessary; nevertheless I think my script's state machine may rely on an race condition that might not be reliable (exit events always triggering before entering events) -- a better approach might be to keep a stack that pushes on enter and pulls on exit; if a frame goes by and the stack is empty, trigger the slice.

Image Radial Fill

Another approach in Unity, if this is to be a UI element, is to use the Image.FillMethod.Radial360 and specify a fillAmount based on the user's mouse inputs. A possible drawback of this approach is that the Image fill Origin is limited out of the box to the four cardinal points; starting the fill elsewhere would require first rotating the image, which might not be desirable if the sprite isn't a plain fill.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .