The Observer Pattern and Event-Driven Games
The observer pattern lets one part of a game announce that something happened without knowing who is listening; it buys freedom between systems, and charges for it in subscriptions that must be carefully cancelled.

In any game of reasonable size there comes a day when a programmer opens the script that handles the player's health and finds that it knows far too much. It updates the health bar, it plays a grunt of pain, it shakes the camera, it tells the achievement system, it notifies the tutorial, it flashes the screen red. Every new feature that cares about damage has reached into that one script and added a line, and now the health component cannot be tested, reused or even read without dragging half the game along with it.
The observer pattern is the classic remedy for this kind of entanglement. Instead of the health component calling every interested system by name, it simply announces that damage was taken, and any system that wishes to know has registered beforehand to be told. The announcer does not know who is listening, how many there are, or what they will do. The pattern appears in the Gang of Four's Design Patterns, and in one form or another it underlies nearly every user interface toolkit and game engine built since.
What follows describes the pattern's two roles, the way C# builds it into the language through delegates and events, Unity's own UnityEvent with its Inspector wiring, the use of events to separate interface from gameplay, the quiet failures that come from forgetting to unsubscribe, and the broader designs, event buses and channels, that grow from the same idea when a project needs announcements to travel further than a single object.
Subjects and observers
The pattern has two parties. The subject is the object that has something to announce, such as a health component, a door, an inventory or a game clock. The observers are the objects that want to be told, such as a health bar, a sound player or a quest tracker. The subject keeps a list of observers, offers a way to add and remove entries, and, whenever the interesting thing happens, walks the list and notifies each one. That is the entire mechanism, and its simplicity is a large part of its appeal.
The important property is the direction of knowledge. The observer knows about the subject, since it had to find it in order to subscribe, but the subject knows nothing about the observer beyond the fact that it can be notified. Dependency therefore points from the many listeners toward the single announcer, rather than outward from the announcer to everything it affects. Adding a new listener, such as a statistics tracker counting how often the player is hurt, requires no change at all to the health component.
There is a cost hidden in that freedom. When the subject notifies its observers, the flow of control leaves the subject's code and enters places the subject cannot see, in an order it does not choose. Reading the health component alone no longer tells you what happens when damage is taken; you must find every subscriber. Debugging becomes a matter of asking who is listening, a question the code does not answer on its own. Event-driven code is easier to extend and harder to follow, and the balance between those two must be weighed honestly.
Notifications also carry a choice about what to send. The subject can merely announce that something changed, leaving each observer to query the details it needs, or it can pass the relevant data along with the notification, such as the new health value and the amount of damage. Passing data is usually more efficient and avoids observers reaching back into the subject, but it fixes the shape of the message, so adding a field later means touching every listener. Most game events settle on a small, carefully chosen payload.
Delegates, events and Action
C# builds the observer pattern into the language. A delegate is a type that holds a reference to a method with a particular signature, and delegates are multicast: one delegate can hold many methods and call them all in turn. The built in Action type spares the programmer from declaring a delegate by hand, with generic variants that carry one or more parameters, so an Action carrying an integer suits a health change. Observers attach a method with the plus-equals operator and detach it with minus-equals.
Marking a delegate field with the event keyword adds an essential restriction. Outside the declaring class, code may only subscribe and unsubscribe; it cannot invoke the event or replace the whole list of subscribers with a single assignment. Without the keyword, any script could fire the health component's damage notification at will, or wipe out every other listener by assigning rather than adding. The event keyword turns a public delegate field into a proper subject interface, and it costs nothing to use.
Raising an event safely is a one line habit. When no observer has subscribed, the delegate is null, and invoking it directly would throw a NullReferenceException. The idiom is to write the event name followed by the null conditional operator and Invoke, which calls the subscribers if there are any and does nothing otherwise. Within the subject, the event is raised at the exact moment the state has changed and is consistent, never halfway through an update, since observers may immediately read the subject's other properties.
UnityEvent and the Inspector
Unity adds its own event type, UnityEvent, which differs from a C# event in one decisive way: it is serializable. A public or serialized UnityEvent field appears in the Inspector as a list where a designer can drag in a target object and choose a method or property to call, with no code at all. The familiar OnClick list on a UI Button is a UnityEvent. A door component with an opened event can, in the editor, be made to play a sound, start a particle effect and enable a trigger, entirely by assignment.
Listeners assigned in the Inspector are called persistent listeners and are saved with the scene or prefab. Listeners can also be attached at runtime through AddListener and detached through RemoveListener, behaving much like a C# event. The two kinds coexist, and RemoveListener affects only the runtime ones, which occasionally surprises people who expect it to clear what they see in the Inspector. Generic forms of UnityEvent carry parameters, and a dynamic option lets the event pass its value through to the chosen method.
The convenience has a price. Inspector wiring is invisible to a text search, so a method that appears unused in the code may in fact be called from some serialized event in a scene no programmer has opened recently, and renaming it silently breaks the link. UnityEvent is also somewhat slower to invoke than a plain C# event. Many teams settle on a division: UnityEvent for designer-facing hooks on reusable components, C# events for the high frequency traffic between systems.
Interface apart from gameplay
The most common and rewarding use of events in games is to separate the user interface from the simulation. A health component should know about health, not about the bar that displays it. When it exposes an event such as HealthChanged carrying the current and maximum values, the health bar subscribes in its own script and updates its fill amount whenever told. The gameplay code never references a single UI type, and the bar never polls the health every frame to see whether anything changed.
The rewards compound. The same health component can now drive a bar over an enemy's head, a portrait in a corner of the screen, a heartbeat sound when health is low and a vignette effect, none of which it knows about. Gameplay can be tested in a scene with no interface at all. The interface can be redesigned, replaced or translated without touching combat code. And an observer that cares about only one moment, such as a sound triggered on death, need not run any code during the many frames when nothing happens.
The same separation applies to any information that flows outward to the player. A resource counter observes the stockpile; a notification panel observes the settlement; a clock display observes the passage of time. In a game like Crown & Ashes, where night brings the Hunger, a single announcement that dusk has fallen could let the lighting, the music and the interface each respond in their own way, without the clock ever having heard of any of them.
Unsubscribing and its ghosts
Every subscription is a reference, and references have consequences. When an observer adds one of its methods to a subject's event, the subject's delegate now holds a reference to that observer. If the subject lives longer than the observer and nobody removes the subscription, the observer cannot be garbage collected while the subject survives. With a static event, which lives for the whole session, every forgotten subscriber stays in memory indefinitely. This is the lapsed listener problem, a leak that grows quietly each time a scene reloads.
In Unity the problem has a sharper edge. Destroying a GameObject destroys the native engine object, but the C# object that wraps it lingers as long as something references it, in a half dead condition where Unity's overloaded equality reports it as null. If the subject later raises its event, the lingering method is called anyway, and the moment it touches its transform or another component, Unity throws a MissingReferenceException. The ghost of a destroyed health bar tries to update itself, and the console fills with errors.
The cure is a discipline rather than a technique. Subscribe in OnEnable and unsubscribe in OnDisable, a symmetrical pair that also handles objects being deactivated and reactivated, or subscribe in Start and unsubscribe in OnDestroy where that suits the lifetime better. Avoid subscribing with anonymous lambdas unless the lambda is stored in a field, because a fresh lambda written in the unsubscribe call is a different object and removes nothing. Treat every plus-equals as a debt that must be paid back with a matching minus-equals.
Event buses and channels
Direct subscription has a limit: the observer must find the subject first. A health bar can be handed a reference to the player, but what of a system that wants to hear about every enemy death in the level, including enemies that do not yet exist? The usual answer is an event bus, a shared hub through which any code can publish a message and any code can subscribe by message type. Publishers and subscribers know only the bus, never each other, and objects can join or leave at any time.
Unity developers often implement this with ScriptableObject event channels, an approach popularized by Ryan Hipple's Unite talk on game architecture with ScriptableObjects. Each channel is an asset, an EnemyDied channel or a NightFell channel, holding an event that components raise and subscribe to through a serialized reference. Because channels are assets rather than scene objects, systems in different scenes or prefabs can communicate without any of them holding a reference to another, and designers can see in the Inspector which components use which channel.
Global buses carry the pattern's costs in concentrated form. When anything can publish and anything can listen, the question of who caused a given reaction can become genuinely hard to answer, and chains of events triggering further events can produce cascades that no one designed. The discipline that keeps them manageable is restraint: a modest number of well named events, clear ownership of who raises each one, logging during development, and a willingness to use a plain method call when two objects truly belong together.

