My LightSwitch script stops working if I place more than one...

Hi guys. I’m very new to coding and have been trying to figure out a couple of things using brute force and reference to the scripting api (where I can understand it). I’ve been trying to make a light switch script using raycasting. I’ve come upon an entirely brand new problem wherein putting two objects with the same script on in a scene make neither of them work. The extra weird thing is that this is NOT the case in my test level (where I wrote the script). Here it is:

public class LightSwitch : MonoBehaviour
{
    [SerializeField] Light myLight;
    [SerializeField] Camera playerCam;
    [SerializeField] float range = 5f;
    [SerializeField] AudioSource audioSource;
    [SerializeField] Material offMaterial;
    [SerializeField] Material onMaterial;

    private void Start()
    {
       onMaterial = myLight.transform.GetComponent<Renderer>().material;
    }

    void Update()
    {
        RaycastHit hit;
        if (Physics.Raycast(playerCam.transform.position, playerCam.transform.forward, out hit, range))
        {
            if (Input.GetKeyDown(KeyCode.E))
            {
                LightSwitch target = hit.transform.GetComponent<LightSwitch>();
                if (target == null) return;
                target.ProcessLight();
                target.ChangeMaterial();
                audioSource.Play();
            }
            else
            {
                return;
            }
        }
        else
        {
            return;
        }
    }

    void ProcessLight()
    {
        myLight.enabled = !myLight.enabled;
    }

    void ChangeMaterial()
    {
        if(myLight.enabled == true)
        {
            myLight.transform.GetComponent<Renderer>().material = onMaterial;
        }
        else
        {
            myLight.transform.GetComponent<Renderer>().material = offMaterial;
            return;
        }
    }
}

Deepest apologies for my generally crappy coding too. I will go back and clear it all up as soon as I get it working, haha.

If anyone can help me with how to get one light switch to toggle an array of lights that would be really cool too.

The first thing that jumps out at me is that your Update attempts to flip any LightSwitch the player uses, not just itself. So if you had 5 of these running in your scene, and the player uses a switch, your 5 scripts will call ProcessLight/ChangeMaterial/Play a total of 5 times on that one switch.

Flipping a switch twice might look a lot like not flipping it at all.

By the way, raycasts are kind of expensive, you probably want to check keydown first and then only cast the ray if the key is pressed.

If you add a third one, it’ll work again. I’m guessing your test level has an odd number of light switches.

Here’s what’s happening. Each frame, each of your LightSwitch instances is running its Update code. It does the raycast, it checks for input, it finds the target’s LightSwitch component, and calls ProcessLight. and then, the second LightSwitch comes along, does all that again… and switches it off.

What you should be doing is, have a single script that does the raycasting and checking for input. That script is what will invoke your LightSwitch’s switching code. You could just go through the list of all interactable objects - if it has a LightSwitch, do this. If it has a DoorKnob, do this. And so on. This would be perfectly functional.

If you want to make that code nicer and less convoluted, you could use inheritance. In that, you’d have a single “InteractableObject” class, and LightSwitch, DoorKnob, etc would all inherit from that class. Now, your single raycasting script described above needs to only check for a single class (InteractableObject), and it calls a Interact() function on that script. LightSwitch and DoorKnob would both override this function and do their own stuff.

public class InteractableObject : MonoBehaviour {
public virtual void Interact() { }
}

//LightSwitch.cs
public class LightSwitch : InteractableObject {
public override void Interact() {
ProcessLight();
ChangeMaterial();
audioSource.Play();
}
// everything else is the same except Update(), which doesn't exist
}

//Interactor.cs
public class Interactor : MonoBehaviour {
void Update() {
RaycastHit hit;
        if (Physics.Raycast(playerCam.transform.position, playerCam.transform.forward, out hit, range))
        {
            if (Input.GetKeyDown(KeyCode.E))
            {
                InteractableObject target = hit.transform.GetComponent<InteractableObject>();
                if (target == null) return;
                target.Interact();
             }
        }
    }
}