There is and there are a few ways to pull it off and they’re pretty much indispensable. Below, whenever *anything valid *overlaps the Opponent, the player will know who was overlapped by whom, even though the player was not the instigator.
Imagine a game that is completely procedurally generated and there’s absolutely nothing in the Level Blueprint, so doing the above also does nothing. Since you do not have references to the pickups that will be randomly popping up, there’s nothing to register at Begin Play. But you can register events dynamically as the objects are spawned (via Actor from Class). This can be done by the blueprint that spawns them (like an item generator responsible for binding and unbinding), by the player, or you can have functionality in the spawned object itself hooking up its elements to a relevant framework class.
As you spawn objects, you register their events and use additional dispatchers if the components’ are not enough and you need to send more data. [HR][/HR]
Not an overkill at all - and in many cases no casting is required. Or you cast only once when the object is spawned to register something inside or set up a dispatcher. Casting is often required when you go heavy on the inheritance. You have a base Pick-up class and several sub classes with unique functionality. You call talk to the base class and call Collected and all children subclasses will know what you’re on about. But when you need to talk to a specific child class and call something unique to them only, you’d need to cast.
What I mean is a Pickup that repairs your ship and a Pickup that is a trap that destroys your ship. They share the same base class and both can be picked up and float about in space (I *know *about your cool little drones o_O), but they do something fundamentally different.
I’ve been using Interfaces for classes that are completely unrelated. Not sure if that’s the right way to do it, but it has worked well for me so far.
The common practice is picking the right tool for the job and eventually you’ll be casting, dispatching events and calling interfaces functions. It’s hard to tell (for me) what works best for what unless you have a specific scenario in mind.