Messages to other objects

Hi there!
I’m trying to send a event message to a few objects. But what I found was only a broadcast to the same object that the script is attached. (Unity - Scripting API: Component.BroadcastMessage)
There is anyway to do what I’m trying to do? Am i looking for the right thing? Am I getting nutts?

I’m planning to build all the logic of the game through messages. Is it right?

[ ]s Chips

TXS 4 your help!

If you want to message scripts other than ones located on the same object as the current script, you need a reference to those scripts or the GameObjects which they’re attached to. You might want to use something like GameObject.FindGameObjectsWithTag() to locate the other objects. This code should broadcast FunctionName to the child hierarchies of all objects with the TagOfYourChoice tag:

var objects : GameObject[] = GameObject.FindGameObjectsWithTag("TagOfYourChoice"); 
for(var oneObject : GameObject in objects)
{
	oneObject.BroadcastMessage("FunctionName");
}

Not sure if you mean to use BroadcastMessage there. BroadcastMessage sends the message to every script on an object and every one of its descendants. That’s potentially a lot of function calls. If you’re just trying to hit every script on a single GameObject, use SendMessage instead.

You could do, but if your game is likely to send messages to lots of objects very frequently, you might find that it becomes too expensive. Also, because it affects every script on a GameObject, you might find yourself calling functions on things when you don’t mean to.

Where possible, it’s usually best to just call the function you want directly:

var objects : GameObject[] = GameObject.FindGameObjectsWithTag("Tag"); 
for(var oneObject : GameObject in objects)
{
	var script = oneObject.GetComponent(YourScript);
	if(script)
	{
		script.FunctionName();
	}
}

On the other hand, messaging can allow for some elegant and economical solutions to otherwise messy problems, so it’s not something that you should rule out altogether. :slight_smile:

Thanks NCarter!
I haven’t used tags yet, but it worked!

about using:

I’ve tryied the GetComponent() for scripts, a few day ago, but when I use more than one script I’ve got an error, something like pushing more than popping something from somewhere… I know that this isn’t much specific but I’ve got kind of traumatized.

[ ]s Chips

We’ve been trying out a message paradigm in our latest game, and it’s working pretty well. We have a lot of events that multiple systems should respond to, and we wanted to generalize it somehow.

This is essentially the Observer pattern. It has two parts:

  • Any GameObject can register itself as a listener for a particular event type. This might be “stunt”, “enemy”, or whatever. It’s just an arbitrary string.

  • Any script can send a message and attach an arbitrary object. For more complicated events we have a class describing the event in detail (so we can send more than one variable).

/*
* Script to manage updating components when certain game events happen - for 
* instance when cars are spawned, deleted, etc
*/

var listeners:Hashtable = new Hashtable();

static var instance:Message;

function Awake()
{
	// Highlander logic
	if(instance != null)
		Debug.Log("Error:  Already an instance of a manager.  Should only be one", this);
	instance = this;
}

/**
* Adds a listener for a particular type of message
*/
function Listen(listenerType:String, go:GameObject)
{
	// if there's no array for this tracking category, make a new one
	if(listeners[listenerType] == null)
	{
		listeners[listenerType] = new ArrayList();
	}
	
	// only add to the array if it isn't already being tracked
	if(!listeners[listenerType].Contains(go))
	{
		listeners[listenerType].Add(go);
	}
}

/**
* Removes a listener for the specified type of message
*/
function StopListen(listenerType:String, component:Component)
{
	if(listeners[listenerType] != null)
	{
		listeners[listenerType].Remove(component);
	}
}

/**
* Sends a message (calls the function denoted by methodName using value value) 
* to all registered listeners for the given message type
*/
function Send(listenerType:String, methodName:String, value:Object)
{
	if(listeners[listenerType] != null)
	{
		for(var listener in listeners[listenerType])
		{
			listener.SendMessage(methodName, value, SendMessageOptions.DontRequireReceiver);
		}
	}
}

So let’s say our sound manager wants to listen to enemy events to play death sounds and such. It just does this in Start():

Message.instance.Listen("enemy", gameObject);

And our enemy controller might broadcast its death as follows:

Message.instance.Send("enemy", "EnemyKilled", new MessageRaptorDeath(this, rb));

So basically we’re extending Unity’s messaging functionality and trying to minimize overhead by only having objects that need messages register for them.

1 Like

Very clever. It’s simple and effective!

This should be put on the wiki :slight_smile:

We’ve been meaning to! After we ship this next game we’ll do a round of wiki postings…

GetComponent doesn’t have anything to do with pushing and popping, so I presume the real source of the error must be elsewhere. I can’t remember what causes this kind of message, though… is it something to do with the new GUI features, or the GL class?

Pushing/popping errors are the new GUI class. You’re probably starting more Begin* statements than you’re ending…

…which could be due to some error between begin/end statements, or that you exit the OnGUI function too early for some reason. (Been there, done that. :wink: )

This is pretty handy stuff! I’ve been using a slightly modified version in a project of my own and have run into something that I was wondering if anyone else might have seen before.

First, I add a few listeners for a given event type. Later, I “Send” an event which has 2 registered listeners. In response to the event, one object removes itself as a listener, while another object sends out a new event. I get an exception thrown that looks like “InvalidOperationException: List has changed.
System.Collections.ArrayList+SimpleEnumerator.MoveNext ()” My guess is that when the one item is removed, it breaks the ‘for each’ loop.

In other scripting languages you can get around this IF you’re doing the remove in the loop by looping backwards, but I’m stumped as to how to make sure the ArrayList isn’t going to change while I’m looping through it.

Anyone have any ideas?

Thanks!
psx

Wow, just stumbled across this script Matthew. This is super slick; thanks for sharing!

I just noticed something, shouldn’t the second argument in the StopListen() method be of a gameObject, not a Component?

I don’t have it in front of me right now, but I’m pretty sure in my modified version I replaced all references to gameObject with Component. This allowed me to have multiple scripts attached to an object and have each one potentially listening for different events. There was another reason I think but can’t remember what it was :slight_smile:

psx

Seriously Matthew, this is the coolest piece of code I’ve seen in months. This is very elegant and I’m learning quite a bit from it as well. Thank you very much.

Very nice and clean code. What I’ve done so far is add child GameObjects with a listener component, with a listener tag on it, to the GameObject needing to receive messages.

I believe the code on the listener script component goes something like this:

public void OnMessage( string method, object value )
{
if( transform.parent == null || transform.parent.gameObject == null )
return;

transform.parent.gameObject.SendMessage( method, value );
}

With the sender using code like so:

GameObject listeners[] = GameObject.FindObjectsWithTag( "Purple Listeners" );
MessageListener listener;

foreach( GameObject go in listeners )
{
listener = go.GetComponent( typeof( MessageListener ) ) as MessageListener;
if( listener != null )
{
listener.OnMessage( "iTellYouThings", this );
}
}

Yeah, you can’t modify a list while iterating through it in a for each loop in .NET. You can get around it (at least one way) by making another list to contain the items to remove, then looping through that list removing the items.

ArrayList origItems = new ArrayList();
origItems.Add("Hello");
origItems.Add("World");
origItems.Add("Goodbye");

ArrayList removeItems = new ArrayList();

foreach(string s in origItems)
{
   if(s == "Goodbye")
   {
      removeItems.Add(s);
   }
}
			
foreach(string s in removeItems)
{
   origItems.Remove(s);
}

I forgot all about that. What I ended up doing was similar. I just made a clone of the array before looping through it and it seems to work fine:

function DispatchEvent(evt : JRMEvent) { 
	
   if(_listeners[evt.eventType] != null) 
   { 
      // have to use a copy of the ArrayList because items could
      // get added or removed while we're in this loop

      var listeners_safe : ArrayList = _listeners[evt.eventType].Clone();
      
      for(var listener in listeners_safe) 
      { 
 
         try {
         	listener.SendMessage(evt.eventName, evt.eventValue, SendMessageOptions.DontRequireReceiver); 
         } catch(e) {
         	// happens when we try to call SendMessage on an object that's been removed/disabled
         	//Debug.Log("DispatchEvent[" + methodName + "] Error : " + e);
         }
      } 
   } 
}

psx