C++
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented C++
-
Section 4: Memory Management
-
Section 5: Templates and Generic Programming
-
Section 6: The Standard Template Library
-
Section 7: Modern C++ Features
-
Section 8: Error Handling
-
Section 9: Multithreading
-
Section 10: Operator Overloading and Type Conversion
-
Section 11: Advanced Topics
-
Section 12: Tooling and Build Systems
-
Section 13: Design Patterns in C++
-
Section 14: Interfacing with C and Systems Programming
-
Section 15: Networking and IPC Basics
-
Section 16: Graphics and Game Programming Basics
-
Section 17: The Boost Libraries
-
Section 18: Data Structures and Algorithms in C++
-
Section 19: Practical Projects
-
Section 20: More Concurrency Patterns
-
Section 21: More OOP and Design Practice
-
Section 22: File I/O and Streams
-
Section 23: More Standard Library
-
Section 24: Practice Exercises
-
Section 25: Interview and Algorithm Practice
-
Section 26: Compiler and Language Internals
-
Section 27: GUI and Application Frameworks Overview
-
Section 28: Testing and Quality Practices
-
Section 29: Numerics Library
-
Section 30: Concepts Library (C++20)
-
Section 31: Ranges Library (C++20) In Depth
-
Section 32: More Utility Library
154: Handling Input and Game State
Up until now, we've looked at how to capture raw input, but in a real game, you rarely just map a key press to a function. If you do, you'll quickly find yourself in "if-statement hell," where you're checking 50 different conditions just to figure out if the player is allowed to jump. The secret is to decouple the input (the key pressed) from the game state (what the character is actually doing).
Defining the Player's Mental Model
I like to start by defining exactly what "modes" my character can be in. For this example, we're building a basic combat controller. A player shouldn't be able to walk while they're mid-swing in a heavy attack, and they certainly shouldn't be able to attack while they're already attacking. I'll use an enum class for this because it keeps the states scoped and prevents naming collisions.
enum class PlayerState { Idle, Moving, Attacking }; class Player { PlayerState currentState = PlayerState::Idle; float attackTimer = 0.0f; public: void handleInput(char input) { // Logic goes here } void update(float deltaTime) { // State-based updates } };Mapping Input to State Transitions
Now, here is where we translate a key press into a state change. I'm going to map 'W' to movement and 'Space' to attacking. Notice how I'm not just moving the character; I'm changing the state. This allows the rest of the game engine to know exactly what the player is doing without checking the keyboard again.
void Player::handleInput(char input) { if (input == 'w') { currentState = PlayerState::Moving; } else if (input == ' ') { currentState = PlayerState::Attacking; attackTimer = 0.5f; // The attack lasts half a second } else { currentState = PlayerState::Idle; } }The "Animation Reset" Bug
I just ran this code in my head, and I realized I made a classic mistake. Look at the
handleInputlogic above. If the player holds down the Space bar,currentStateis set toAttackingandattackTimeris reset to 0.5f every single frame. In a real game, this would cause the attack animation to restart constantly, leaving the player stuck on frame one of the animation. It's a jittery mess.The fix is to implement "state guards." We only transition to a state if the current state allows it. I need to make sure that if we're already attacking, the input is ignored until the action finishes.
Locking States for Fluidity
Here is the corrected version. I've added checks to ensure that we don't interrupt an attack and that we only enter the
Movingstate if we aren't currently locked in an action. I'm also adding theupdatemethod to handle the timer that eventually pushes us back toIdle.void Player::handleInput(char input) { // If we are attacking, ignore all other inputs until the timer hits zero if (currentState == PlayerState::Attacking) { return; } if (input == 'w') { currentState = PlayerState::Moving; } else if (input == ' ') { currentState = PlayerState::Attacking; attackTimer = 0.5f; } else { currentState = PlayerState::Idle; } } void Player::update(float deltaTime) { if (currentState == PlayerState::Attacking) { attackTimer -= deltaTime; if (attackTimer <= 0) { currentState = PlayerState::Idle; } } }By shifting the logic this way, the
handleInputfunction becomes a "request" to change state, and the state machine decides if that request is valid. This is the foundation of almost every professional action game; whether it's a simple 2D platformer or a massive open-world RPG, they're all just managing transitions between states like this.
📋 Practical Task
Implementing a Timed Dodge-Roll State
Expand the Player class from the lesson to include a "Dodge-Roll" mechanic. Your implementation must meet the following criteria:
- Add
DodgeRollingto thePlayerStateenum. - Map the 'Shift' key (or a character of your choice) to trigger the dodge roll.
- The dodge roll should last for 0.3 seconds.
- While
DodgeRolling, the player should be unable toAttackorMove. - Once the dodge timer expires, the player should automatically return to the
Idlestate.
Write the updated handleInput and update methods to manage this new state and its transition logic.
There are no comments for now.