- Home /
acting on other object during collision: SendMessage or GetComponent?
Common thing that happens in a million different games: A projectile hits something, and then does something to whatever it hits (damage, plays a sound, flashes color, etc). The reaction to the collision is different depending on the components attached to the objects involved, e.g. a piece of terrain wouldn't take damage. From what I know, there are two ways to deal with this: SendMessage(), a bunch of GetComponent() calls, or checking tags followed by either method calls depending on the tag. All of these work, but also seem to have their own problems.
SendMessage() seems like by far the most elegant and simple. the sender doesn't have to care what scripts the other object has, but if any of them have the method, it gets called. However this would result in a lot of SendMessage() calls that don't do anything, plus I've heard that SendMessage() is very slow, so a high-action moment with dozens of collisions happening in a single frame could take a big performance hit.
A bunch of GetComponent() calls to achieve the same thing seems a lot less elegant, but might perform better. What I mean is something like
void OnCollisionEnter(Collision other)
{
IDamageable damageable = other.gameObject.GetComponent<IDamageable>();
if (damageable != null) {//do damage stuff}
IHitFlashable hitFlash = other.gameObject.GetComponent<IHitFlashable>();
if (hitFlash != null) {//do hitflash stuff}
//and so on for any other relevant scripts
}
This is a lot less elegant looking, but I understand that GetComponent<>() might be significantly faster than SendMessage(). However this still has the problem of making a bunch of unnecessary calls.
finally, there is the option of limiting either the SendMessage() or GetComponent<>() calls by checking the tag of the other object and making calls depending on what the tag is. However, then you run into the problem of needing a different tag for each different combination of relevant scripts which could be attached to the other object, and end up with a real messy pile of tags to keep track of.
SO my question is, which way do y'all prefer? or is there a pattern for this situation that I haven't thought of or read about that trumps them all? I realize that the difference between each method might be so small that its better to leave to personal preference, but I'm the kind of person who likes to do everything the 'best' way :]
Nice comparison. I cannot tell you which is the proper way, but I can tell you about my typical setup.
1) Bullet know what can take damage (by tag), tag usage is limited to let set (enemy, environment) 2) When bullet hits enemy I take the component via interface (lets say IDamageable), I am sure that every enemy tag will have script that extends this interface 3) I do only one call passing collision point as argument (which is useful for some situations i.e. spawn blood on hit point) i.e. TakeDamage(Vector3 hitPosition); 4) The rest of the getting hit I do already on enemy side (normally I have an enum with the enemy type) and I execute damage effects differently for each based on this enum. As you said "The reaction to the collision is different depending on the components attached to the objects involved" but the components attached are normally related to the type of object that gets hit, and not the opposite way.
As it goes for the best way, I am afraid there isn't one, even if some code ninja would convince you one solution allocates less than another, it will work differently with i.e. more components etc.. And the elegant looking code, you cannot really measure that :)
Answer by jdean300 · Jun 20, 2016 at 07:28 AM
Avoid the tags mess. If you're going to have a bunch of different special cases, then trying to use tags to solve this problem is going to become a nightmare.
I'd prefer the GetComponent approach for a few reasons
Compile time checking (SendMessage won't notice when you type "DamgeTargt")
Explicitly describes the code. SendMessage goes by method name - once your project grows to many thousands of lines of code, can you remember every component that has a method by that name? And when adding a new method to a class, you have to consider whether or not that method could be called in a SendMessage call. You might be able to remember this stuff while regularly working on the project, but good luck if you take a break for a few weeks.
Performance. This really does depend on your game and the target platform, but GetComponent is going to be faster. I have seen wildly different results from others' test results - some people say SendMessage is hundreds of times slower than GetComponent, others say it is only two or three times slower. Again, it comes down to platform and the specifics of your game.
The other option you might consider is having some kind EventRouter component inside of each gameObject. Then, your bullets collision code could be this:
void OnCollisionEnter(Collision other)
{
var eventRouter = other.gameObject.GetComponent<EventRouter>();
if (eventRouter == null)
return;
eventRouter.SendEvent<HitByBulletEvent>(new HitByBulletEvent(/*whatever data you need*/));
}
And the components on a gameObject can subscribe to events with the EventRouter, either through delegates or function pointers. I'd prefer function pointers
private Dictionary<Type, List<Action<Object>>>> m_EventSubscribers;
public void Subscribe<T>(Action<Object> action)
{
if (!m_EventSubscribers.ContainsKey(typeof(T)))
m_EventSubscribers.Add(typeof(T), new List<Action<Object>>());
m_EventSubscribers[typeof(T)].Add(action);
}
public void SendEvent<T>(Object eventData)
{
foreach(var action in m_EventSubscribers[typeof(T)])
{
m_EventSubscribers(eventData);
}
}
The tradeoffs here are more memory and a bit more code, but you'll get better performance than SendMessage without having to make a mess of all your collision code. Performance wise, I would not be surprised if this was competitive with GetComponent calls - it would depend on how GetComponent was implemented.
To make it faster, use an enum to keep track of event types, then you don't have to call typeof constantly. But then you have maintenance of the enum.
Your answer
Follow this Question
Related Questions
How to reference compound colliders? 1 Answer
how to make one script change a variable in another scipt 2 Answers
Distribute terrain in zones 3 Answers
Multiple Cars not working 1 Answer
Variables from one script to another. 3 Answers