Ben Traje
← Back to unity

How to Build a Start and Restart Game Loop in Unity (New Input System)

13 Aug 26 (2mo ago)

Setting up a basic game flow—pausing the action until a player presses a button and restarting upon game over—is a foundational requirement for arcade-style games. Doing this cleanly with Unity's New Input System requires freezing world time and handling multi-device input checks.

Here is how to create a robust start and restart lo op that supports keyboards, mice, and gamepads (like Xbox, PlayStation, or 8BitDo controllers).

1. Controlling Game State with Time.timeScale

The most direct way to pause player movement, physics, and object spawners simultaneously is by manipulating Time.timeScale.

Setting Time.timeScale = 0f stops Unity's physics engine (FixedUpdate) and reduces Time.deltaTime to zero, which freezes movement scripts. Standard Update() loops continue to run, allowing your scripts to listen for start or restart inputs while the game world remains frozen.

2. Detecting Multi-Device Input

Unlike the legacy Input.anyKeyDown API, Unity's New Input System treats keyboards, mice, and gamepads as distinct device classes (Keyboard.current, Mouse.current, and Gamepad.current). To support a "press any button" feature across all devices, you must explicitly query them.

Here is a complete, drop-in GameManager script:

using UnityEngine;
using TMPro;
using UnityEngine.InputSystem;
using UnityEngine.SceneManagement;
using System.Linq; // Required for Gamepad LINQ queries

public class GameManager : MonoBehaviour
{
    public static GameManager instance;
    public TMP_Text scoreText;
    public float Score = 0;

    public bool isGameStarted = false;
    private bool isGameOver = false;

    private void Awake()
    {
        if (instance == null) { instance = this; }
        else { Destroy(gameObject); }
    }

    void Start()
    {
        // Freeze the game world on launch
        Time.timeScale = 0f;
        scoreText.text = "Press Any Button to Start";
    }

    private void Update()
    {
        if (!isGameStarted || isGameOver)
        {
            if (HasAnyInputPressed())
            {
                if (isGameOver) RestartGame();
                else StartGame();
            }
        }
    }

    private bool HasAnyInputPressed()
    {
        bool keyboard = Keyboard.current != null && Keyboard.current.anyKey.wasPressedThisFrame;
        bool mouse = Mouse.current != null && Mouse.current.leftButton.wasPressedThisFrame;
        
        // Uses System.Linq to check all gamepad controls
        bool gamepad = Gamepad.current != null && Gamepad.current.allControls.Any(
            x => x is UnityEngine.InputSystem.Controls.ButtonControl button && button.wasPressedThisFrame
        );

        return keyboard || mouse || gamepad;
    }

    void StartGame()
    {
        isGameStarted = true;
        isGameOver = false;
        Time.timeScale = 1f; // Resume gameplay
        Score = 0;
        scoreText.text = "Score: " + Score;
    }

    public void GameOver()
    {
        isGameOver = true;
        scoreText.text = "Game Over. Press Any Button to Restart";
        Time.timeScale = 0f; // Freeze gameplay
    }

    void RestartGame()
    {
        // Cleanly resets the scene
        SceneManager.LoadScene(SceneManager.GetActiveScene().name);
    }
}

3. Key Technical Nuances & Troubleshooting

  • Fixing the CS1061 Error (LINQ): Reading Gamepad.current.allControls returns a ReadOnlyArray<InputControl>. Using .Any() on this collection requires adding using System.Linq; at the top of your script to prevent compilation errors.
  • The No-LINQ Alternative: If you prefer to avoid LINQ entirely, you can replace the gamepad boolean logic with a safe foreach loop:
bool gamepadPressed = false;
if (Gamepad.current != null)
{
    foreach (var control in Gamepad.current.allControls)
    {
        if (control is UnityEngine.InputSystem.Controls.ButtonControl button && button.wasPressedThisFrame)
        {
            gamepadPressed = true;
            break;
        }
    }
}

  • Controller Compatibility (8BitDo/PC): For maximum compatibility on PC, ensure third-party controllers (like 8BitDo) are set to X-Input ("X") mode. If inputs aren't registering, open Unity's Input Debugger (Window > Analysis > Input Debugger) to verify the engine is successfully detecting the hardware.
  • Scene Reset Strategy: Reloading the active scene via SceneManager.LoadScene() cleanly resets object positions and local variables without manual cleanup. Just remember that Time.timeScale state persists across scene loads, so ensure your script explicitly resets it back to 1f upon restarting.