Popcorning Guinea Pig
Search Results for

    Input System

    The Popcorning Guinea Pig engine features a sophisticated input system built on the command pattern, providing flexible input handling through contextual bindings, priority-based resolution, and command execution. The system supports keyboard, mouse, and gamepad inputs with full customization and PGPSCN integration.

    Overview

    The input system provides:

    • Command Pattern: Decoupled input handling through command objects
    • Context Stack: Priority-based input context layering (menus over gameplay)
    • Input Bindings: Flexible key-to-command mappings with modifiers
    • Multi-Device Support: Keyboard, mouse, and gamepad input
    • PGPSCN Integration: Define input bindings declaratively
    • Real-time Polling: High-performance input state tracking
    • Custom Commands: Extensible command system

    Core Architecture

    Input Flow

    Raw Input → InputManager → InputKey → ContextStack → InputBinding → InputCommand → Execute
    

    Key Components

    1. InputManager: Low-level input polling and event generation
    2. ContextStack: Priority-based context resolution
    3. InputContext: Named collections of input bindings
    4. InputBinding: Maps input keys to commands
    5. InputCommand: Executable actions triggered by input

    Input Manager

    The InputManager handles raw input polling and state tracking:

    using PopcorningGuineaPig.Engine.Input;
    
    public class GameInputManager
    {
        private InputManager inputManager;
        
        public void Initialize()
        {
            inputManager = new InputManager();
            inputManager.OnInputChanged += HandleInputEvent;
        }
        
        public void Update()
        {
            inputManager.Update();
        }
        
        private void HandleInputEvent(InputEvent inputEvent)
        {
            // Process input changes
            Console.WriteLine($"Input: {inputEvent.Key}, Trigger: {inputEvent.Trigger}");
        }
    }
    

    Input Events

    Input events contain:

    • InputKey: The key/button that changed
    • InputTriggerType: Pressed, Released, or Held
    • Modifiers: Ctrl, Shift, Alt combinations
    public enum InputTriggerType
    {
        Pressed,
        Released,
        Held
    }
    
    [Flags]
    public enum InputModifiers
    {
        None = 0,
        Ctrl = 1,
        Shift = 2,
        Alt = 4
    }
    

    Input Keys

    Input keys represent different input sources:

    using PopcorningGuineaPig.Engine.Input;
    
    // Keyboard input
    var moveUp = InputKey.Keyboard(KeyboardKey.W, InputModifiers.None);
    var quickSave = InputKey.Keyboard(KeyboardKey.S, InputModifiers.Ctrl);
    
    // Mouse input  
    var primaryClick = InputKey.Mouse(MouseButton.Left, InputModifiers.None);
    var contextMenu = InputKey.Mouse(MouseButton.Right, InputModifiers.None);
    
    // Gamepad input
    var jump = InputKey.Gamepad(GamepadButton.A, gamepadIndex: 0);
    var pause = InputKey.Gamepad(GamepadButton.Start, gamepadIndex: 0);
    

    Input Commands

    Commands implement the IInputCommand interface:

    public interface IInputCommand
    {
        string Id { get; }
        bool CanExecute(object[] parameters = null);
        Task ExecuteAsync(object[] parameters = null);
        void Execute(object[] parameters = null);
    }
    

    Basic Command Implementation

    public class MovePlayerCommand : IInputCommand
    {
        public string Id => "move_player";
        
        private readonly PlayerController player;
        private readonly Vector3 direction;
        
        public MovePlayerCommand(PlayerController player, Vector3 direction)
        {
            this.player = player;
            this.direction = direction;
        }
        
        public bool CanExecute(object[] parameters = null)
        {
            return player != null && player.CanMove;
        }
        
        public void Execute(object[] parameters = null)
        {
            if (CanExecute(parameters))
            {
                player.Move(direction);
            }
        }
        
        public async Task ExecuteAsync(object[] parameters = null)
        {
            Execute(parameters);
            await Task.CompletedTask;
        }
    }
    
    public class SaveGameCommand : IInputCommand
    {
        public string Id => "save_game";
        
        public bool CanExecute(object[] parameters = null)
        {
            return GameManager.CanSave;
        }
        
        public void Execute(object[] parameters = null)
        {
            if (CanExecute())
            {
                GameManager.SaveGame();
                ShowNotification("Game Saved!");
            }
        }
        
        public async Task ExecuteAsync(object[] parameters = null)
        {
            if (CanExecute())
            {
                await GameManager.SaveGameAsync();
                ShowNotification("Game Saved!");
            }
        }
    }
    

    Scene Method Commands

    Execute methods on scene objects:

    public class SceneMethodCommand : IInputCommand
    {
        public string Id { get; }
        private readonly Scene scene;
        private readonly string methodName;
        private readonly object[] methodParameters;
        
        public SceneMethodCommand(string id, Scene scene, string methodName, object[] parameters = null)
        {
            Id = id;
            this.scene = scene;
            this.methodName = methodName;
            this.methodParameters = parameters;
        }
        
        public bool CanExecute(object[] parameters = null)
        {
            return scene != null;
        }
        
        public void Execute(object[] parameters = null)
        {
            if (CanExecute())
            {
                var method = scene.GetType().GetMethod(methodName);
                method?.Invoke(scene, methodParameters);
            }
        }
        
        public async Task ExecuteAsync(object[] parameters = null)
        {
            Execute(parameters);
            await Task.CompletedTask;
        }
    }
    

    Input Contexts

    Contexts group related input bindings with priority:

    using PopcorningGuineaPig.Engine.Input;
    
    public class GameplayInputContext : InputContext
    {
        public GameplayInputContext() : base("gameplay", priority: 100)
        {
            SetupGameplayBindings();
        }
        
        private void SetupGameplayBindings()
        {
            // Movement
            AddBinding(InputKey.Keyboard(KeyboardKey.W), new MovePlayerCommand(player, Vector3.Forward));
            AddBinding(InputKey.Keyboard(KeyboardKey.A), new MovePlayerCommand(player, Vector3.Left));
            AddBinding(InputKey.Keyboard(KeyboardKey.S), new MovePlayerCommand(player, Vector3.Back));
            AddBinding(InputKey.Keyboard(KeyboardKey.D), new MovePlayerCommand(player, Vector3.Right));
            
            // Actions
            AddBinding(InputKey.Keyboard(KeyboardKey.Space), new JumpCommand(player));
            AddBinding(InputKey.Mouse(MouseButton.Left), new AttackCommand(player));
            
            // System
            AddBinding(InputKey.Keyboard(KeyboardKey.Escape), new OpenPauseMenuCommand());
            AddBinding(InputKey.Keyboard(KeyboardKey.S, InputModifiers.Ctrl), new SaveGameCommand());
        }
    }
    
    public class MenuInputContext : InputContext
    {
        public MenuInputContext() : base("menu", priority: 200) // Higher priority than gameplay
        {
            AllowPassthrough = false; // Block lower priority contexts
            SetupMenuBindings();
        }
        
        private void SetupMenuBindings()
        {
            AddBinding(InputKey.Keyboard(KeyboardKey.Up), new NavigateMenuCommand("up"));
            AddBinding(InputKey.Keyboard(KeyboardKey.Down), new NavigateMenuCommand("down"));
            AddBinding(InputKey.Keyboard(KeyboardKey.Enter), new SelectMenuItemCommand());
            AddBinding(InputKey.Keyboard(KeyboardKey.Escape), new CloseMenuCommand());
        }
    }
    

    Context Stack

    The context stack manages input priority and resolution:

    using PopcorningGuineaPig.Engine.Input;
    
    public class InputSystem
    {
        private ContextStack contextStack;
        private InputManager inputManager;
        
        public void Initialize()
        {
            contextStack = new ContextStack();
            inputManager = new InputManager();
            
            // Set up global context for system-wide commands
            var globalContext = new InputContext("global", priority: 0);
            globalContext.AddBinding(
                InputKey.Keyboard(KeyboardKey.F11), 
                new ToggleFullscreenCommand());
            contextStack.SetGlobalContext(globalContext);
            
            inputManager.OnInputChanged += ProcessInputEvent;
        }
        
        public void PushContext(InputContext context)
        {
            contextStack.Push(context);
        }
        
        public void PopContext(string contextName)
        {
            contextStack.Pop(contextName);
        }
        
        private void ProcessInputEvent(InputEvent inputEvent)
        {
            if (inputEvent.Trigger == InputTriggerType.Pressed)
            {
                var binding = contextStack.ResolveBinding(inputEvent.Key);
                if (binding != null && binding.Command.CanExecute())
                {
                    binding.Command.Execute();
                }
            }
        }
        
        public void Update()
        {
            inputManager.Update();
        }
    }
    

    Context Layering Example

    public class GameScene : Scene
    {
        private InputSystem inputSystem;
        private GameplayInputContext gameplayContext;
        private MenuInputContext menuContext;
        
        public override void Load()
        {
            inputSystem = new InputSystem();
            inputSystem.Initialize();
            
            // Always active gameplay context
            gameplayContext = new GameplayInputContext();
            inputSystem.PushContext(gameplayContext);
        }
        
        public void OpenPauseMenu()
        {
            // Menu context takes priority over gameplay
            menuContext = new MenuInputContext();
            inputSystem.PushContext(menuContext);
        }
        
        public void ClosePauseMenu()
        {
            // Remove menu context, gameplay resumes
            inputSystem.PopContext("menu");
            menuContext = null;
        }
    }
    

    Input Bindings

    Bindings connect input keys to commands:

    public class InputBinding
    {
        public InputKey Key { get; }
        public IInputCommand Command { get; }
        public bool Enabled { get; set; } = true;
        
        public InputBinding(InputKey key, IInputCommand command)
        {
            Key = key;
            Command = command;
        }
    }
    
    // Usage
    var binding = new InputBinding(
        InputKey.Keyboard(KeyboardKey.Space), 
        new JumpCommand(player)
    );
    
    // Conditional enabling
    binding.Enabled = player.IsAlive && player.OnGround;
    

    Dynamic Binding Modification

    public class InputBindingManager
    {
        private Dictionary<string, InputBinding> bindings = new();
        
        public void AddBinding(string id, InputKey key, IInputCommand command)
        {
            bindings[id] = new InputBinding(key, command);
        }
        
        public void RemoveBinding(string id)
        {
            bindings.Remove(id);
        }
        
        public void EnableBinding(string id, bool enabled = true)
        {
            if (bindings.TryGetValue(id, out var binding))
            {
                binding.Enabled = enabled;
            }
        }
        
        public void RebindKey(string id, InputKey newKey)
        {
            if (bindings.TryGetValue(id, out var binding))
            {
                bindings[id] = new InputBinding(newKey, binding.Command);
            }
        }
    }
    

    PGPSCN Integration

    Define input bindings in scene files:

    {
      "version": "1.0",
      "sceneType": "2d",
      "input_contexts": [
        {
          "name": "gameplay",
          "priority": 100,
          "allowPassthrough": true,
          "bindings": [
            {
              "key": {
                "type": "keyboard",
                "key": "W",
                "modifiers": []
              },
              "command": {
                "type": "scene_method",
                "method": "MovePlayer",
                "parameters": ["forward"]
              }
            },
            {
              "key": {
                "type": "keyboard", 
                "key": "Space"
              },
              "command": {
                "type": "scene_method",
                "method": "PlayerJump"
              }
            },
            {
              "key": {
                "type": "mouse",
                "button": "Left"
              },
              "command": {
                "type": "scene_method",
                "method": "PlayerAttack"
              }
            },
            {
              "key": {
                "type": "gamepad",
                "button": "A",
                "gamepadIndex": 0
              },
              "command": {
                "type": "scene_method",
                "method": "PlayerJump"
              }
            }
          ]
        },
        {
          "name": "menu",
          "priority": 200,
          "allowPassthrough": false,
          "bindings": [
            {
              "key": {
                "type": "keyboard",
                "key": "Up"
              },
              "command": {
                "type": "scene_method", 
                "method": "NavigateMenu",
                "parameters": ["up"]
              }
            },
            {
              "key": {
                "type": "keyboard",
                "key": "Escape"
              },
              "command": {
                "type": "scene_method",
                "method": "CloseMenu"
              }
            }
          ]
        }
      ],
      "global_bindings": [
        {
          "key": {
            "type": "keyboard",
            "key": "F11"
          },
          "command": {
            "type": "static_method",
            "class": "DisplayManager",
            "method": "ToggleFullscreen"
          }
        },
        {
          "key": {
            "type": "keyboard",
            "key": "S",
            "modifiers": ["Ctrl"]
          },
          "command": {
            "type": "static_method",
            "class": "GameManager", 
            "method": "SaveGame"
          }
        }
      ]
    }
    

    Loading Input Contexts from PGPSCN

    public class InputContextLoader
    {
        public void LoadFromPGPSCN(string scenePath, Scene scene, InputSystem inputSystem)
        {
            var sceneData = JsonSerializer.Deserialize<SceneData>(File.ReadAllText(scenePath));
            
            // Load input contexts
            foreach (var contextDef in sceneData.InputContexts ?? new())
            {
                var context = new InputContext(contextDef.Name, contextDef.Priority)
                {
                    AllowPassthrough = contextDef.AllowPassthrough
                };
                
                // Load bindings
                foreach (var bindingDef in contextDef.Bindings)
                {
                    var inputKey = CreateInputKey(bindingDef.Key);
                    var command = CreateCommand(bindingDef.Command, scene);
                    
                    context.AddBinding(inputKey, command);
                }
                
                inputSystem.PushContext(context);
            }
            
            // Load global bindings
            if (sceneData.GlobalBindings?.Any() == true)
            {
                var globalContext = new InputContext("global", priority: 0);
                
                foreach (var bindingDef in sceneData.GlobalBindings)
                {
                    var inputKey = CreateInputKey(bindingDef.Key);
                    var command = CreateCommand(bindingDef.Command, scene);
                    
                    globalContext.AddBinding(inputKey, command);
                }
                
                inputSystem.SetGlobalContext(globalContext);
            }
        }
        
        private InputKey CreateInputKey(InputKeyDefinition keyDef)
        {
            var modifiers = ParseModifiers(keyDef.Modifiers);
            
            return keyDef.Type switch
            {
                "keyboard" => InputKey.Keyboard(Enum.Parse<KeyboardKey>(keyDef.Key), modifiers),
                "mouse" => InputKey.Mouse(Enum.Parse<MouseButton>(keyDef.Button), modifiers),
                "gamepad" => InputKey.Gamepad(Enum.Parse<GamepadButton>(keyDef.Button), keyDef.GamepadIndex),
                _ => throw new NotSupportedException($"Input type '{keyDef.Type}' not supported")
            };
        }
        
        private IInputCommand CreateCommand(CommandDefinition cmdDef, Scene scene)
        {
            return cmdDef.Type switch
            {
                "scene_method" => new SceneMethodCommand(
                    cmdDef.Method, scene, cmdDef.Method, cmdDef.Parameters),
                "static_method" => new StaticMethodCommand(
                    cmdDef.Class, cmdDef.Method, cmdDef.Parameters),
                _ => throw new NotSupportedException($"Command type '{cmdDef.Type}' not supported")
            };
        }
    }
    

    Advanced Features

    Input Buffering

    Buffer inputs for combo systems and precise timing:

    public class InputBuffer
    {
        private readonly Queue<InputEvent> buffer = new();
        private readonly float bufferDuration;
        private readonly Dictionary<InputEvent, DateTime> timestamps = new();
        
        public InputBuffer(float bufferDurationSeconds = 0.2f)
        {
            bufferDuration = bufferDurationSeconds;
        }
        
        public void AddInput(InputEvent inputEvent)
        {
            buffer.Enqueue(inputEvent);
            timestamps[inputEvent] = DateTime.Now;
            
            CleanOldInputs();
        }
        
        public bool ConsumeInput(InputKey expectedKey)
        {
            var toRemove = new List<InputEvent>();
            bool found = false;
            
            foreach (var inputEvent in buffer)
            {
                if (inputEvent.Key.Equals(expectedKey))
                {
                    found = true;
                    toRemove.Add(inputEvent);
                    break;
                }
            }
            
            // Remove consumed input
            foreach (var eventToRemove in toRemove)
            {
                var tempQueue = new Queue<InputEvent>();
                while (buffer.Count > 0)
                {
                    var evt = buffer.Dequeue();
                    if (evt != eventToRemove)
                        tempQueue.Enqueue(evt);
                }
                while (tempQueue.Count > 0)
                    buffer.Enqueue(tempQueue.Dequeue());
                    
                timestamps.Remove(eventToRemove);
            }
            
            return found;
        }
        
        private void CleanOldInputs()
        {
            var cutoff = DateTime.Now.AddSeconds(-bufferDuration);
            var toRemove = new List<InputEvent>();
            
            foreach (var kvp in timestamps)
            {
                if (kvp.Value < cutoff)
                    toRemove.Add(kvp.Key);
            }
            
            foreach (var eventToRemove in toRemove)
            {
                timestamps.Remove(eventToRemove);
                // Remove from buffer as well
                var tempQueue = new Queue<InputEvent>();
                while (buffer.Count > 0)
                {
                    var evt = buffer.Dequeue();
                    if (evt != eventToRemove)
                        tempQueue.Enqueue(evt);
                }
                while (tempQueue.Count > 0)
                    buffer.Enqueue(tempQueue.Dequeue());
            }
        }
    }
    

    Combo System

    Implement input combos using sequences:

    public class ComboSystem
    {
        private readonly List<ComboSequence> combos = new();
        private readonly InputBuffer inputBuffer = new();
        
        public void RegisterCombo(ComboSequence combo)
        {
            combos.Add(combo);
        }
        
        public void ProcessInput(InputEvent inputEvent)
        {
            inputBuffer.AddInput(inputEvent);
            
            // Check all combos
            foreach (var combo in combos)
            {
                if (combo.CheckSequence(inputBuffer))
                {
                    combo.Execute();
                    break; // Only execute first matching combo
                }
            }
        }
    }
    
    public class ComboSequence
    {
        public InputKey[] Sequence { get; }
        public IInputCommand Command { get; }
        public float MaxTimeBetweenInputs { get; }
        
        public ComboSequence(InputKey[] sequence, IInputCommand command, float maxTime = 1.0f)
        {
            Sequence = sequence;
            Command = command;
            MaxTimeBetweenInputs = maxTime;
        }
        
        public bool CheckSequence(InputBuffer buffer)
        {
            // Implementation to check if the buffer contains the required sequence
            // within the time limit
            return false; // Simplified for brevity
        }
        
        public void Execute()
        {
            if (Command.CanExecute())
            {
                Command.Execute();
            }
        }
    }
    
    // Usage
    var fireballCombo = new ComboSequence(
        new[] { 
            InputKey.Keyboard(KeyboardKey.Down),
            InputKey.Keyboard(KeyboardKey.Right), 
            InputKey.Keyboard(KeyboardKey.A) 
        },
        new FireballCommand(),
        maxTime: 0.8f
    );
    comboSystem.RegisterCombo(fireballCombo);
    

    Input Recording and Playback

    Record and replay input sequences:

    public class InputRecorder
    {
        private List<TimestampedInput> recording = new();
        private bool isRecording = false;
        private DateTime recordingStart;
        
        public void StartRecording()
        {
            isRecording = true;
            recordingStart = DateTime.Now;
            recording.Clear();
        }
        
        public void StopRecording()
        {
            isRecording = false;
        }
        
        public void RecordInput(InputEvent inputEvent)
        {
            if (isRecording)
            {
                var elapsed = (DateTime.Now - recordingStart).TotalSeconds;
                recording.Add(new TimestampedInput(inputEvent, elapsed));
            }
        }
        
        public async Task PlaybackRecording(InputSystem inputSystem)
        {
            var startTime = DateTime.Now;
            
            foreach (var timestampedInput in recording)
            {
                var elapsed = (DateTime.Now - startTime).TotalSeconds;
                var waitTime = timestampedInput.Timestamp - elapsed;
                
                if (waitTime > 0)
                {
                    await Task.Delay(TimeSpan.FromSeconds(waitTime));
                }
                
                // Simulate input event
                inputSystem.ProcessInputEvent(timestampedInput.InputEvent);
            }
        }
    }
    
    public class TimestampedInput
    {
        public InputEvent InputEvent { get; }
        public double Timestamp { get; }
        
        public TimestampedInput(InputEvent inputEvent, double timestamp)
        {
            InputEvent = inputEvent;
            Timestamp = timestamp;
        }
    }
    

    Best Practices

    Performance Optimization

    1. Efficient Context Management: Only push/pop contexts when necessary
    2. Command Caching: Reuse command instances where possible
    3. Input Filtering: Filter irrelevant inputs early
    4. Batch Processing: Group input events in high-frequency scenarios
    public class OptimizedInputSystem
    {
        private readonly Dictionary<string, IInputCommand> commandCache = new();
        private readonly HashSet<InputKey> relevantKeys = new();
        
        public void RegisterRelevantKey(InputKey key)
        {
            relevantKeys.Add(key);
        }
        
        private void ProcessInputEvent(InputEvent inputEvent)
        {
            // Filter irrelevant inputs
            if (!relevantKeys.Contains(inputEvent.Key))
                return;
                
            // Use cached commands
            var binding = contextStack.ResolveBinding(inputEvent.Key);
            if (binding?.Command != null)
            {
                var commandId = binding.Command.Id;
                if (!commandCache.ContainsKey(commandId))
                {
                    commandCache[commandId] = binding.Command;
                }
                
                commandCache[commandId].Execute();
            }
        }
    }
    

    Input Validation

    public abstract class ValidatedInputCommand : IInputCommand
    {
        public abstract string Id { get; }
        
        public bool CanExecute(object[] parameters = null)
        {
            return ValidateInput(parameters) && CanExecuteInternal(parameters);
        }
        
        protected abstract bool ValidateInput(object[] parameters);
        protected abstract bool CanExecuteInternal(object[] parameters);
        
        public void Execute(object[] parameters = null)
        {
            if (CanExecute(parameters))
            {
                ExecuteInternal(parameters);
            }
        }
        
        protected abstract void ExecuteInternal(object[] parameters);
        
        public async Task ExecuteAsync(object[] parameters = null)
        {
            if (CanExecute(parameters))
            {
                await ExecuteAsyncInternal(parameters);
            }
        }
        
        protected abstract Task ExecuteAsyncInternal(object[] parameters);
    }
    
    public class MovePlayerValidatedCommand : ValidatedInputCommand
    {
        public override string Id => "move_player_validated";
        
        protected override bool ValidateInput(object[] parameters)
        {
            return parameters?.Length >= 1 && parameters[0] is Vector3;
        }
        
        protected override bool CanExecuteInternal(object[] parameters)
        {
            return GameManager.Player?.CanMove == true;
        }
        
        protected override void ExecuteInternal(object[] parameters)
        {
            var direction = (Vector3)parameters[0];
            GameManager.Player.Move(direction);
        }
        
        protected override async Task ExecuteAsyncInternal(object[] parameters)
        {
            ExecuteInternal(parameters);
            await Task.CompletedTask;
        }
    }
    

    Context Management

    public class ContextManager
    {
        private readonly InputSystem inputSystem;
        private readonly Stack<string> contextHistory = new();
        
        public ContextManager(InputSystem inputSystem)
        {
            this.inputSystem = inputSystem;
        }
        
        public void PushContextWithHistory(InputContext context)
        {
            contextHistory.Push(context.Name);
            inputSystem.PushContext(context);
        }
        
        public void PopToLastContext()
        {
            if (contextHistory.Count > 1)
            {
                var currentContext = contextHistory.Pop();
                inputSystem.PopContext(currentContext);
            }
        }
        
        public void PopAllContexts()
        {
            while (contextHistory.Count > 0)
            {
                var contextName = contextHistory.Pop();
                inputSystem.PopContext(contextName);
            }
        }
    }
    

    Integration Examples

    Complete Input Setup

    public class GameInputSetup
    {
        private InputSystem inputSystem;
        private GameplayInputContext gameplayContext;
        private MenuInputContext menuContext;
        
        public void Initialize(Scene scene)
        {
            inputSystem = new InputSystem();
            inputSystem.Initialize();
            
            SetupGameplayContext(scene);
            SetupMenuContext(scene);
            SetupGlobalContext();
        }
        
        private void SetupGameplayContext(Scene scene)
        {
            gameplayContext = new GameplayInputContext();
            
            // Player movement
            gameplayContext.AddBinding(
                InputKey.Keyboard(KeyboardKey.W),
                new MovePlayerCommand(scene.Player, Vector3.Forward));
                
            // Combat
            gameplayContext.AddBinding(
                InputKey.Mouse(MouseButton.Left),
                new AttackCommand(scene.Player));
                
            inputSystem.PushContext(gameplayContext);
        }
        
        private void SetupMenuContext(Scene scene)
        {
            menuContext = new MenuInputContext();
            // Menu bindings setup...
        }
        
        private void SetupGlobalContext()
        {
            var globalContext = new InputContext("global", 0);
            
            globalContext.AddBinding(
                InputKey.Keyboard(KeyboardKey.F11),
                new ToggleFullscreenCommand());
                
            globalContext.AddBinding(
                InputKey.Keyboard(KeyboardKey.Escape),
                new QuitGameCommand());
                
            inputSystem.SetGlobalContext(globalContext);
        }
        
        public void Update()
        {
            inputSystem.Update();
        }
        
        public void SwitchToMenu()
        {
            inputSystem.PushContext(menuContext);
        }
        
        public void SwitchToGameplay()
        {
            inputSystem.PopContext("menu");
        }
    }
    

    The input system in Popcorning Guinea Pig provides a powerful, flexible foundation for handling all types of user input with clean separation of concerns, priority-based resolution, and extensive customization options.

    Further Reading

    • PGPSCN Format - Declarative input binding definition
    • Game Architecture - Core engine architecture
    • UI Components - UI input integration
    • Settings Management - Input preferences and keybinding
    • Edit this page
    In this article
    Back to top © 2024 Popcorning Guinea Pig Game Engine. A custom C# game engine for indie developers.