3
\$\begingroup\$

Okay, so I asked this question about 2 years ago, and revisiting my project, I'm amazed I still can't think of a solution. I seemingly can write basic adventure game engines from scratch, but Unity's component-based programming stumps me.

So basically I have two cubes, SourceCube and RedirectCube. SourceCube is just a cube that fires a laser (for now a raycast).

A redirect cube is a cube that, when hit by a sourcecube laser, will fire its own laser.

My question is, how can I detect when one redirect cube's laser hits another redirect cube, causing the second redirect cube to fire its own laser (basically like the cubes in Portal 2)?

Below in the image, the blue-faced cube is my source cube. The pink ones are the redirect cubes.

enter image description here

Here's the code for the sourceCube (Unity, C#):

using UnityEngine;
using System.Collections;

public class OriginLaser : MonoBehaviour 
{
    public RaycastHit sourceOut;

    void Update()
    {
        SourceShoot();   
    }

    void SourceShoot()  
    {
        if(Physics.Raycast(transform.position, -transform.forward, out sourceOut, Mathf.Infinity))
        {
            Debug.DrawRay(transform.position, -transform.forward * 20000, Color.red); 

        }
    }
}

Pretty self-explanatory. Just draw a raycast.

Here's the code for the redirect Cube:

using UnityEngine;
using System.Collections;

public class RedirecCubetLaser : MonoBehaviour 
{

    public RaycastHit redirectHit;
    public OriginLaser originRef;

    void Start()
    {
        originRef = GameObject.FindGameObjectWithTag("Source").GetComponent<OriginLaser>(); 

    }

    void Update()
    {
        Redirect(); 
    }

    void Redirect()
    {
        if (originRef.sourceOut.collider.name == this.gameObject.name)
        {
            if(Physics.Raycast(transform.position, -transform.forward, out redirectHit, Mathf.Infinity))
            {
                Debug.DrawRay(transform.position, -transform.forward * 20000, Color.green); 

            }
        }
    }
}

And this works for detecting sourceCube rays. My question is, how can I get it to detect rays from another instance of the same object (these cubes will eventually become prefabs)?

Say we have two identical objects, RedirectCube1 and RedirectCube2. If sourceCube's ray activates RedirectCube1, how does RedirectCube1 activate RedirectCube2?

\$\endgroup\$
  • \$\begingroup\$ The fact that you have accepted an answer makes it 'solved'. No need to state it in the title :) \$\endgroup\$ – Alexandre Vaillancourt Jun 23 '16 at 11:18
2
\$\begingroup\$

I would make the laser a separate entity in this situation. Instead of the cube update redirecting the laser, the laser entity does all the tracking in one go:

Pseudocode:

Vector3 current_direction;
Vector3 current_location;
while (true) {
    RaycastHit hit;
    if (RaycastAgainstCubes(current_location, current_direction, out hit)) {
        // mark hit, update direction and location
    }
    else {
        // didn't hit another cube, done
        break;
    }
}

This isn't completely robust -- you may want to mark hit cubes and break if you hit the same cube again (to prevent an infinite loop), but the general concept should work.

\$\endgroup\$
2
\$\begingroup\$

It may be simpler to make the redirect cube a combined toggle-able laser source and laser detector. Where the source is active if the detector is hit.

That way the SourceShoot just sets the detector.hit = true and the redirect cube's shoot is only called if(this.detector.hit)

\$\endgroup\$
0
\$\begingroup\$

Alright, thanks for the answers, but I found a decent solution. Honestly, I'm amazed I didn't think of this before. It seems ridiculously obvious. Anyway, the OriginLaser code remains untouched, here's the Redirect code:

using UnityEngine;
using System.Collections;

public class Redirect : MonoBehaviour 
{
    public bool isActive;
    public OriginLaser sourceCube;
    public RaycastHit redirectHit;

    void Start()
    {
        isActive = false;
        sourceCube =  GameObject.FindGameObjectWithTag("Source").GetComponent<OriginLaser>();
    }

    void Update()
    {
        if (sourceCube.sourceOut.collider.gameObject == this.gameObject || isActive == true)
        {
            if(Physics.Raycast(transform.position, -transform.forward, out redirectHit, Mathf.Infinity))
            {
                Debug.DrawRay(transform.position, -transform.forward * 20000, Color.green); 
            }

            if (redirectHit.collider.tag == "Redirect")
            {
                redirectHit.collider.gameObject.GetComponent<Redirect>().isActive = true;
            }
        }

    }
}

This line is key:

if (redirectHit.collider.tag == "Redirect")

Simply, if this specific redirect cube (that is already activated) detects another redirect cube, activate it. For any redirect cube, if activated, create a laser.

I tried something like this before, but did it the opposite, like with detecting the source cube (that is, the redirect cube object looks for source cube rays. Here the redirect cube object activates another redirect cube, rather than the second redirect cube looking for the first redirect cube's ray). Makes sense?

edit: this presents a minor bug with not being able to turn off the cubes when not being detected. ill post a fix later

edit2: here's the fixed version. definitely not a good solution, I repeat code and stuff, but whatever.

using UnityEngine;
using System.Collections;

public class Redirect : MonoBehaviour 
{
    public bool isActive;
    public bool isFirst;
    public OriginLaser sourceCube;
    public Redirect otherCube;
    public RaycastHit redirectHit;




    void Start()
    { 

        isActive = false;
        sourceCube =  GameObject.FindGameObjectWithTag("Source").GetComponent<OriginLaser>();
        otherCube = null;

    }

    void Update()
    {

        isFirst = (sourceCube.sourceOut.collider.gameObject == this.gameObject);

        if (isFirst || isActive)
        {
            if(Physics.Raycast(transform.position, -transform.forward, out redirectHit, Mathf.Infinity))
            {
                Debug.DrawRay(transform.position, -transform.forward * 20000, Color.green); 
            }




            if ((redirectHit.collider.tag == "Redirect") && (!redirectHit.collider.gameObject.GetComponent<Redirect>().isActive && !redirectHit.collider.gameObject.GetComponent<Redirect>().isFirst))                  
            {


                if (otherCube != null && redirectHit.collider.gameObject != otherCube.gameObject && otherCube.isActive == true) 
                {
                    otherCube.isActive = false; 

                    if (otherCube.otherCube != null)
                    {
                        otherCube.otherCube.isActive = false;
                    }
                    otherCube = null;

                }

                otherCube = redirectHit.collider.gameObject.GetComponent<Redirect>();


                otherCube.isActive = true;

                }

            }



        }


        if (otherCube != null && ((redirectHit.collider.gameObject != otherCube.gameObject && otherCube.isActive) || (!isFirst && !isActive)))
        {


            otherCube.isActive = false; 

            if (otherCube.otherCube != null)
            {
                    otherCube.otherCube.isActive = false;
            }

            otherCube = null;



        }





    }
}

edit3: awful solution above. last edit. Here's a better one. (3 scripts)

using UnityEngine;
using System.Collections;

public class CubeProperties : MonoBehaviour
{
    public bool isActive;
    public string cubeColor;
    public CubeProperties otherCube;

    public void Start()
    {
        isActive = false;
        cubeColor = "red";
        otherCube = null;
    }

}

-

using UnityEngine;
using System.Collections;

public class OriginLaser : CubeProperties 
{
    public RaycastHit sourceOut;

    public LineRenderer line;

    void Start()
    {
        base.Start();

        line = GetComponent<LineRenderer>();
    }


    void Update()
    {
        SourceShoot();   
    }

    void SourceShoot()  
    {
        if(Physics.Raycast(transform.position, -transform.forward, out sourceOut, Mathf.Infinity))
        {
            line.SetVertexCount(2);
            line.SetPosition(0, transform.position);
            line.SetPosition(1, sourceOut.point);
        }



        if (otherCube != null && otherCube.isActive && sourceOut.collider.gameObject != otherCube.gameObject)
        {
            otherCube.isActive = false;
            otherCube = null;
        }



        if (sourceOut.collider.tag.Contains("Gamelogic") && !sourceOut.collider.gameObject.GetComponent<CubeProperties>().isActive)
        {
            otherCube = sourceOut.collider.gameObject.GetComponent<CubeProperties>();
            otherCube.isActive = true;
        }

    }
}

-

using UnityEngine;
using System.Collections;

public class Redirect : CubeProperties 
{
    public RaycastHit redirectHit;
    public LineRenderer line;

    void Start()
    {
        base.Start();
        line = GetComponent<LineRenderer>();
    }

    void Update()
    {
        RedirectLaser();
    }



    void RedirectLaser()
    {


        if (otherCube != null && otherCube.isActive)
        {
            if ((redirectHit.collider.gameObject != otherCube.gameObject) || !isActive)
            {
                otherCube.isActive = false;
                otherCube = null;
            }
        }


        if (otherCube != null && otherCube.otherCube != null && otherCube.otherCube.gameObject == this.gameObject && otherCube.otherCube.isActive && isActive)
        {
            otherCube.isActive = false;
            otherCube.otherCube.isActive = false;
            otherCube = null;
        }





        if (isActive)
        {
            if(Physics.Raycast(transform.position, -transform.forward, out redirectHit, Mathf.Infinity))
            {
                line.SetVertexCount(2);
                line.SetPosition(0, transform.position);
                line.SetPosition(1, redirectHit.point);
            }


            if ((redirectHit.collider.tag.Contains("Gamelogic")) && 
                (!redirectHit.collider.gameObject.GetComponent<CubeProperties>().isActive) &&
                (cubeColor == redirectHit.collider.gameObject.GetComponent<CubeProperties>().cubeColor))
            {

                if (otherCube != null && otherCube.isActive && redirectHit.collider.gameObject != otherCube.gameObject)
                {
                    otherCube.isActive = false;
                }

                otherCube = redirectHit.collider.gameObject.GetComponent<CubeProperties>();
                otherCube.isActive = true;
            }
        }

        if (!isActive)
        {
            line.SetVertexCount(0);
        }







    }
}

The only reason I have a CubeProperties class is because I'm going to implement more cubes later, that shares some properties with Redirect. That is why I put Contains("Gamelogic"), so any redirect cube can detect any 'logic' cube, and vice-versa. I repeat code, but that can easily be changed by putting it into CubeProperties. I didn't do it yet because I want to create more cubes first. Oh, and ignore the "cubeColor" stuff. For the purpose of my game I'll have different colored cubes that can only activate other cubes.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.