Search code examples
c#unity-game-enginetagstransformmesh-collider

Change public Transforms based on collision. Change line renderer points based on public transforms


I'm pretty new and I can't seem to get this to work. Players have a long pole and if they poke certain objects it starts a string-like connection to the next one they poke. These objects are tagged as "PokableObjects", and to poke players will click. I'm going to have hundreds of different pokable objects, and I want the script on the pole to work for all of them.

I think I'm misunderstanding how to reference only the objects being poked. I want the points of a Bezier Curve script, which are public Transforms, to adapt and become whatever "PokableObject" the player clicks.

This is my script:

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

public class BCurve : MonoBehaviour
{
    //curved line renderer stuff
    private LineRenderer lineRenderer;
    public Transform p0;
    public Transform p1;
    public Transform p2;

    //Object name detection stuff
    bool m_Started;
    public LayerMask m_LayerMask;

    //Respawn Stuff
    private float clickCounter;
    public GameObject newPoker;

    void Start()
    {
        lineRenderer = GetComponent<LineRenderer>();
        m_Started = true;
        clickCounter = 0;
    }

    private void OnTriggerStay(Collider other)

    {
        if (other.tag == "PokableObject")

        {
            Collider[] hitColliders = Physics.OverlapBox(gameObject.transform.position, transform.localScale / 2, Quaternion.identity, m_LayerMask);

            if (Input.GetMouseButtonDown(0) && (clickCounter == 0))
            {
                p0 = Collider.gameObject.position;
                clickCounter++;
            }
                
  
            else
            {
                p2 = Collider.gameObject.position;

                //find midpoint between p0 & p2 then lower it's Y coordinate by 1
                p1 = ((p0.position.x + p2.position.x) * .05f, ((p0.position.y + p2.position.y) * .05f) - 1), (p0.position.z + p2.position.z) * .05f;

                //disable current object and spawn a new one so players can repeat
                Instantiate(newPoker, transform.position, Quaternion.Euler(0, 0, 0));
                GetComponent<BCurve>().enabled = false;
            }
        }
    }

    void Update()
    {
        DrawQuadraticBezierCurve(p0.position, p1.position, p2.position);

    }

    void DrawQuadraticBezierCurve(Vector3 p0, Vector3 p1, Vector3 p2)

    {
        lineRenderer.positionCount = 200;
        float t = 0f;
        Vector3 B = new Vector3(0, 0, 0);
        for (int i = 0; i < lineRenderer.positionCount; i++)
        {
            B = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
            lineRenderer.SetPosition(i, B);
            t += (1 / (float)lineRenderer.positionCount);
        }
    }
}

All help is much appreciated.

Thanks


Solution

    • First of all never compare float values using ==! Due to floating point (im)precision this might fail even though logically you think it would match.

      E.g.

       0.2f * 5f / 10f == 1.0f
      

      is not necessarily true because it might actually be 1.0000001 or 0.9999999.

      This said anyway it makes no sense to use a float for a counter. You rather want to use

      int clickCounter;  
      
    • Then you have a bunch of , there when calculating p1. Seems like this should have been wrapped in a p1 = new Vector3( .... )

    • However anyway the p1 you calculate is no Transform but would rather be a Vector3 position. You don't need this as a field! Simply recalculate it later given the two Transforms p0 and p2 within DrawQuadraticBezierCurve and then you can simply make it

      var p1 = (p0.position + p2.position) * .05f - Vector3.up;
      
    • There is no need for that LayerMask and OverlapBox which result you are not using anywhere at all. You already have a tag to check your hit

    • Collider.gamObject makes no sense. What you want is access the Transform of the object you are colliding with which simply is the other.Transform

    • There is no need for GetComponent<BCurve>. This component already is the BCurve so you could simply use

      enabled = false;
      
    • However, as soon as you get the second point you are disabling the component so Update will never be called again.

      Either you want to draw the line only once with the positions in that moment so simply call DrawQuadraticBezierCurve right away and remove your entire Update method.

      Or if your objects might continue moving and you want to continuously update the line then keep Update but keep this component enabled.

    • In both cases you should only call DrawQuadraticBezierCurve once you already have both Transforms.


    So all together it could look like this

    public class BCurve : MonoBehaviour
    {
        //curved line renderer stuff
        [SerializeField] private LineRenderer lineRenderer;
    
        public Transform start;
        public Transform end;
    
        //Respawn Stuff
        public GameObject newPoker;
    
        // I would make this adjustable via the Inspctor
        [SerializeField] private int positionCount = 200;
    
        private void Start()
        {
            if(!lineRenderer) lineRenderer = GetComponent<LineRenderer>();
        }
    
        private void OnTriggerStay(Collider other)
        {
            if(start && end)
            {
                return;
            }
    
            // in general rather use CompareTag instead of ==
            // the second silently fails in case of typos and is slightly slower
            if (other.CompareTag("PokableObject"))
            {
                // Then you ALWAYS want to check fr the mouse click, not only 
                // if the counter is 0
                if(Input.GetMouseButtonDown(0))
                {
                    // you rather want to check here
                    if (!start)
                    {
                        start = other.transform;
                    }
                    // I would add a check though to not enable to click on the same object twice
                    else if (other.transform != start)
                    {
                        end = other.transform;
    
                        // If you anyway want the line to be drawn only ONCE with the current positions 
                        // then directly call this here and delete Update completely
                        //DrawQuadraticBezierCurve(start.position, end.position);
                        //enabled = false;
                        // or even
                        //Destroy(this);
    
                        Instantiate(newPoker, transform.position, Quaternion.Euler(0, 0, 0));
                    }
                }
            }
        }
    
        private void Update()
        {
            if(start && end)
            {
               DrawQuadraticBezierCurve(start.position, end.position);
            }
        }
    
        private void DrawQuadraticBezierCurve(Vector3 p0, Vector3 p2)
        {
            lineRenderer.positionCount = positionCount;
            var inversePositionCount = 1f / positionCount;
            var t = 0f;
    
            var p1 = (p0.position + p2.position) * .05f - Vector3.up;
            var positions = new Vector3[positionCount];
    
            for (int i = 0; i < lineRenderer.positionCount; i++)
            {
                var B = (1 - t) * (1 - t) * p0 + 2 * (1 - t) * t * p1 + t * t * p2;
                positions[i] = B;
                t += inversePositionCount;
            }
    
            // Note: It is WAY cheaper to call this and set all positions at once
            lineRenderer.SetPositions(positions);
        }
    }