Search Unity

Question Is there any faster way than calling SendMessage?

Discussion in 'Scripting' started by pus2meong, Nov 19, 2022.

  1. pus2meong

    pus2meong

    Joined:
    May 3, 2012
    Posts:
    83
    Hi, I have a design problem with my code. I hope everyone can give me suggestion regarding this.

    My game is an adventure game where player can interact with NPC, door, item on ground, etc.
    When the player is touching the NPC, door, item on ground, the TriggerEnter from the object will assign a string and gameobject to my InputScript.

    So if the player is touching a NPC, it will assign a string with the value of function name that related to the NPC, in this case: open a dialog box.
    Let's just say, the function name is: OpenDialogBox and I put it into a string variable named functionName.
    And since the function is resided inside the NPC object, I assign the NPC transform into a transform variable named: targetObject

    When player press the A button on Xbox controller, my InputScript will call SendMessage with the assigned string as the parameter. And a dialog box will appear.

    So the code is basically like this

    Code (CSharp):
    1. targetObject.Sendmessage(functionName);
    With this design, I can expect any random function that can be assigned without the need to use switch (which is limited).

    Problem is, SendMessage can be very expensive on a slow device.
    For a slow pace game probably will not matter anymore. But for fast pace game, when player need to spam the buttons in order to defeat the enemy as fast ast possibly or to make a combo like in a fighting game, I'm worried if sendmessage is taking too much resources.

    So, is there any better way for deal with this problem?
    Is there any better way to assign a function that can be called later with a single line of code like SendMessage? but still maintain the design that you can call it without worried with the function name and the object where the script is located?
     
  2. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,990
    Well, just use UnityEvents. They can be serialized and assigned in the inspector. So you can wire up everything in the inspector. It's not really clear how you actually use SendMessage at the moment since you currently rely on a string binding and we don't know where you actually set the string.
     
  3. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,853
    Looking at some of your requirements, I'd definitely take a look at Interfaces or delegates here; probably the latter in your case.

    Rather than a string, you could subscribe a method to a delegate of which is invoked by player input. A whole lot more type safe and performant than the old school SendMessage.
     
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    For the OpenDialogueBox thing, the normal programming thing is just
    target.OpenDialogueBox()
    .

    For where you aren't sure which part of the target needs the message, the normal programming thing is to be sure -- for example, make sure the target's main script has OpenDialogueBox, or have it so a Dialogue component will have it.
    target.GetComponent<DialogueBox>().Open()
    is more complicated to figure out -- that's why SendMessage was invented. But it's fine once you get used to it.

    It sounds like your script has stuff like:
    Code (CSharp):
    1. if(...) command="run";
    2. else if(..) command="attack"
    3.  
    4. target.SendMessage(command);
    That's the same as:
    Code (CSharp):
    1. if(...) target.run();
    2. else if(...) target.attack()
    Take a look at older threads about SendMessage. They say the same thing -- SendMessage is a beginner's command that quickly turns into "arrg! don't do it that way".
     
  5. pus2meong

    pus2meong

    Joined:
    May 3, 2012
    Posts:
    83
    SendMessage will be called from my InputScript.
    So when my player touch any interactable object, that interactable object will call a function that will assign two variable values (string & transform) into a string and a transform variable inside my InputScript.

    I'm avoiding any manual assignment from the inspector. Because, for example if I was expecting to call one from 6 different function, when another one need to be assigned, I have to assign manually.

    With my design, I don't have to get stuck with that limitation. Since I only need to assign the function name in a string variable and the transform of where the related function is located. Using those two variables (string and transform), I could just use SendMessage from my InputScript to call the current assigned function.



    I will try to look into this delegates stuff. Hopefully it will solve my concern. Thanks.
     
  6. pus2meong

    pus2meong

    Joined:
    May 3, 2012
    Posts:
    83
    Ah, sorry I think I'm not explain this that clear.

    target.OpenDialogueBox()

    target is a transform, not a script. So I cannot call OpenDialogBox that way.
    If I use the normal programming, I have to GetComponent the related script and then call directly the OpenDialogBox.

    target.GetComponent<TheAttachedScript>().OpenDialogueBox()


    If I do this, I will need alot of if -else or switch to anticipate the variety of script and functions that I might need to call.

    If I have three different scripts and each script will have different function name. And if those three scripts can be triggered with the same button (but in different condition/area), I need to put those scripts inside if-else or switch (or maybe inside UnityEvents), this is what I'm avoiding at all cost. Because I have to add more whenever new function need to be called by the same button even if it in different condition.

    With my design:
    Three different script:
    Ascript.cs (with Afunction)
    Bscript.cs (with Bfunction)
    Cscript.cs (with Cfunction)

    When my player touch an object that contain Bscript.cs in it, the Bscript.cs will call a function from my InputScript.cs and it will assign the object transform into a variable named: target, and also assign the string value "Bfunction" to a variable named: functionName

    Both target and functionName variables are located inside my InputScript.
    And when player hit the button, my InputScript will just call

    target.SendMessage(functionName)

    So when player touch different script, it will just do the same without too many if-else or any type of switching and branching.

    And as with my first post, I tried to find a better way but with the same function assignment flexibility. because SendMessage can be too expensive on old devices when any spamming button press need to be done.
     
  7. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,853
    Here's a simple example to illustrate what I mean:

    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3.  
    4. public class PlayerInput : Monobehaviour
    5. {
    6.     public event Action OnPlayerInteract;
    7.    
    8.     private void Update()
    9.     {
    10.         if (Input.GetButtonDown("Interact"))
    11.         {
    12.             OnPlayerInteract?.Invoke();
    13.         }
    14.     }
    15. }
    16.  
    17. public class DoorInteractable : Monobehaviour
    18. {
    19.     private void OnTriggerEnter(Collider other)
    20.     {
    21.         if (other.TryGetComponent(out PlayerInput playerInput))
    22.         {
    23.             playerInput.OnPlayerInteract += OpenCloseDoor;
    24.         }
    25.     }
    26.    
    27.     private void OnTriggerExit(Collider other)
    28.     {
    29.         if (other.TryGetComponent(out PlayerInput playerInput))
    30.         {
    31.             playerInput.OnPlayerInteract -= OpenCloseDoor;
    32.         }
    33.     }
    34.    
    35.     private void OpenCloseDoor()
    36.     {
    37.         //open close door here
    38.     }
    39. }
    This is just to illustrate the concept as to how you'd use delegates, of course.

    But to answer your question in a word, is there a better way than SendMessage? Yes.

    SendMessage is just a relic of Unity's past.
     
  8. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    Classic c# events have a huge drawback though they have no decoupling between subscribe and publisher. A event bus or event aggregator is much more decoupled.
     
  9. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,853
    It's just a basic example of a concept. No need to get too complicated just yet.
     
    Max-om likes this.
  10. pus2meong

    pus2meong

    Joined:
    May 3, 2012
    Posts:
    83
    Thanks for the code example. I can understand how to use it.

    I was wondering, is it possible to add the function name using plain string or put the function name into a variable and then added to the action?

    Code (CSharp):
    1. playerInput.OnPlayerInteract += OpenCloseDoor;
    So my idea is to put OpenCloseDoor into a variable or something and then added to the action.
     
  11. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,853
    The point is not to use a string because they're bad practice. You want to keep things type safe and free of errors due to typos.

    I'm already doing that in the code example. It's called a delegate.
     
  12. pus2meong

    pus2meong

    Joined:
    May 3, 2012
    Posts:
    83
    I think I need to change my approach if I want to use delegate.
    Thanks for the reply.
     
  13. Max-om

    Max-om

    Joined:
    Aug 9, 2017
    Posts:
    499
    Why? Unity send message is reverted from c# the publisher needs to know about the listener. Thats about it


    Edit: really akward system to be honest.
     
    Last edited: Nov 19, 2022
  14. pus2meong

    pus2meong

    Joined:
    May 3, 2012
    Posts:
    83
    With SendMessage, this the function that I use to assign any trigger that can be called by my gamepad buttons.

    Code (CSharp):
    1. public void SetTrigger(Selector.GamepadButton gbutton, Selector.ButtonPushType ptype,Transform target,string name)
    There are four parameter:
    1. GamepadButton to determine which button that can trigger the function (A,B,X,Y in Xbox controller).
    2. ButtonPushType to determine what kind push that will trigger the function, is it press, hold, or release?
    3. Target a transform reference on where the necessary script with the function is located.
    4. Name, the name of the function in string.

    Based on spiney199 code sample, to delegate a function into an action we need to write the function name, and cannot use string or stored in a variable first, I have to change on how I should set the function with delegate.
     
  15. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,997
    Sure. And most people do this, using programming tricks to make it simpler. There could be front-end functions handling all of those IF's:
    Code (CSharp):
    1. bool OpenDialogueBox(Transform target, int option) {
    2.   Dialoguer d = target.GetComponent<Dialoguer>();
    3.   if(d==null) return false;
    4.   return d.showDialogue(option);
    5. }
    Or there could be a common script on every "actionable" object which does the work and all you ever do is
    target.GetComponent<interactableObject>().run();
    .

    To make a script do different things in different areas, some people just have it check. Or you could go with the delegate idea (probably set once as the object is created in area type X) ...and so on.