In this lesson, you will learn how to transition between game states using event-driven delegates instead of constantly checking conditions in Tick.
The Problem with Polling
Beginners often use Tick to check game states, writing code like if (bIsGameOver) { DoSomething(); } every frame. This is inefficient and creates "spaghetti code" where objects are tightly coupled. Instead, we use the Event-Driven State Machine pattern: objects simply "listen" for a state change and react only when that change occurs.
Implementing the Pattern
To build this, define an enumeration for your states and a Multicast Delegate that broadcasts whenever the state changes. Instead of polling, interested objects bind to this delegate.
// Define our states
UENUM()
enum class EGameState { Idle, Playing, GameOver };
// The State Manager
class UGameStateManager : public UObject {
public:
// Delegate to broadcast state changes
DECLARE_MULTICAST_DELEGATE_OneParam(FOnStateChanged, EGameState);
FOnStateChanged OnStateChanged;
void SetState(EGameState NewState) {
CurrentState = NewState;
// Broadcast the event to all listeners instantly
OnStateChanged.Broadcast(CurrentState);
}
private:
EGameState CurrentState;
};
Why This Architecture Scales
When an enemy needs to stop attacking because the game is over, it doesn't need to ask the game manager "Is it over yet?" every frame. By binding a function to the OnStateChanged delegate, the enemy remains dormant until the manager explicitly shouts that the state has changed.
This decoupling ensures your game logic only executes when absolutely necessary, drastically improving performance. Because we use delegates, the GameStateManager doesn't even need to know the Enemy class exists; it simply broadcasts its message into the ether, and the Enemy catches it. This makes your system modular, testable, and highly efficient.
Key Takeaway: Replace constant frame-based polling with event-driven delegates to ensure your game logic only reacts when state transitions actually occur.