Home > Software design >  How to assign multiple functions to a button at different times?
How to assign multiple functions to a button at different times?

Time:09-29

I want a button to do multiple things but not at the same time.

For e.g. in the game, when the player comes near the door and press the button, i want it to open the door. And when player comes near a weapon and press the SAME button, i want the player to pick up the weapon.

P.S. I'm making a game for mobile.

CodePudding user response:

You could for example use an enum and set a value/type on it. You can set a value when you are near a door, for example, and overwrite it when you are near a weapon.

As soon as the button is pressed, you simply check the value of the enum and execute an action based on the value.

enum Actions
{
    OpenDoor,
    NearWeapon
}

CodePudding user response:

You actually do not need different callbacks for this.

I would have one single callback react differently to whatever you are close to.

This is pretty much the same as here. Slightly different use case but the principle is similar.

There is basically two main option

  • Either the target objects implement the logic => use a common interface/base class
  • Your player implements the logic => use whatever to differ between the object types

Then your player could check what it "is close to" - I will just assume physics again but how exactly you check what you are "close to" is up to you - and interact with that target object.


Interface

You could simply have a shared interface like e.g.

public interface IInteractable
{
    void Interact(/*pass in whatever arguments you need e.g.*/Player player);
}

and then have your classes implement it and the logic e.g.

public class Door : MonoBehaviour, IInteractable
{
    public void Interact(Player player)
    {
        // whatever
    }
}

and

public class Weapon : MonoBehaviour, IInteractable
{
    public void Interact(Player player)
    {
        // whatever
    }
}

and then assuming you use physics (triggers) you could do e.g.

public class Player : Monobehaviour
{
    [SerialzieField] private Button button;

    // we will store multiple close objects so we can smoothly transition between them in case 
    // we are close to multiple ones - up to you of course
    private readonly List<IInteractable> currentActives = new();

    private void Awake()
    {
        i(!button) button = GetComponent<Buton>();

        // you attach a snigle callback
        button.onClick.AddListener(Interact);
    }

    private void OnTriggerEner(Collider other)
    {
        // does this object have an IInteractable 
        var interactable = other.GetComponentInParent<IInteractable>();

        if(interactable == null) return;

        currentCloses.Add(interactable);
    }

    private void OnTriggerExit(Collider other)
    {
        // was the object an IInteractable?
        var interactable = other.GetComponentInParent<IInteractable>(true);

        if(interactable == null) return;

        currentCloses.Remove(interactable);
    }

    private void Interact()
    {
        // some Linq magic to pick the closest item - again up to you
        var currentClosest = currentCloses.Select(item => (MonoBehaviour)item).OrderBy(item => (item.transform.position, transform.position).sqrMagnitude).FirstOrDefault();

        if(currentClosest == null) return;

        // now let that object handle the interaction
        // this class doesn't need to know what exactly this means at all
        currentClosest.Interact(this);
    }
}

This is of course a little bit inflexible and you always need to have a common shared interface and pass along all needed parameters. It can also get quite dirty if e.g. the weapon implements its own "pick up" - doesn't feel quite right.


Different Types

You could simply have different types (components) on your target objects, lets say e.g.

// have a common shared base class
public abstract class Interactable : MonoBeaviour
{
}

and then have derived types

public class Door : Interactable
{
    // they can either implement their own logic
    public void Open()
    {
        // whatever
    }
}

and e.g.

public class Weapon : Interactable
{
    // this one is purely used to differ between the type
    // we rather expect whoever uses this will implement the logic instead
}

and then pretty similar as above

public class InteractionController : Monobehaviour
{
    [SerialzieField] private Button button;

    private readonly List<Interactable> currentActive = new();

    private void Awake()
    {
        i(!button) button = GetComponent<Buton>();

        button.onClick.AddListener(Interact);
    }

    private void OnTriggerEner(Collider other)
    {
        var interactable = other.GetComponentInParent<Interactable>();

        if(!interactable) return;

        currentCloses.Add(interactable);
    }

    private void OnTriggerExit(Collider other)
    {
        var interactable = other.GetComponentInParent<Interactable>();

        if(!interactable) return;

        currentCloses.Remove(interactable);
    }

    private void Interact()
    {
        var currentClosest = currentCloses.OrderBy(item => (item.transform.position, transform.position).sqrMagnitude).FirstOrDefault();

        if(currentClosest == null) return;
        
        // just that this one you check which actual type this component has
        // and deal with it accordingly, completely customizable
        switch(currentActive)
        {
            case Door door: 
                door.Open();
                break;

            case Weapon weapon:
                PickupWeapon(weapon);
                break;

            default:
                Debug.LogError($"Interaction with {currentActive.GetType().AssemblyQualifiedName} is not implemented!");
                break;
        }
    }

    // Just as example that the target doesn't even need to implement the method itself
    private void PickupWeapon(Weapon weapon)
    {
        // whatever
    }
}
  • Related