Tuning game balance with parameters: difficulty without frustration

2025-12-22~9 min read

Why parameters matter

For small games, the difference between “fun” and “frustrating” often comes down to tuning. I try to expose parameters so I can iterate quickly and keep changes understandable.

What I parameterize

  • Throw power curve and clamp
  • Physics materials (friction, restitution) per object
  • Special mechanics triggers (e.g., every N throws)
  • Camera limits and control sensitivity

GameParams Implementation Example

export const GameParams = {
  // Plate settings
  plate: {
    radius: 0.67,        // Plate radius
    height: 0.033,      // Plate height
    thickness: 0.015,   // Plate thickness
    rimThickness: 0.047, // Rim collision thickness
    rimWallHeight: 0.117, // Rim collision height
  },

  // Ball settings
  ball: {
    radius: 0.15,                    // Ball radius
    mass: 0.3,                       // Mass
    connectionDistance: 0.35,       // Connection detection distance
    idealConnectionDistance: 0.3,    // Ideal connection distance
    squashY: 1.0,                   // Y-axis squash amount
    colliderSquashY: 0.75,          // Collider Y-axis squash amount
    colliderRadiusScale: 0.95,      // Collider radius multiplier
  },

  // Physics settings
  physics: {
    friction: 0.25,                  // Friction coefficient (0.0-1.0)
    restitution: 0.0,               // Restitution coefficient (0.0-1.0)
    linearDamping: 0.15,            // Linear damping
    angularDamping: 0.55,           // Angular damping
    contactEquationStiffness: 1e6,  // Contact "hardness"
    contactEquationRelaxation: 4,    // Contact relaxation
    frictionEquationStiffness: 1e6,  // Friction "hardness"
    frictionEquationRelaxation: 4,   // Friction relaxation
  },

  // Camera settings
  camera: {
    distance: 3,                    // Distance from plate
    height: 2,                     // Camera height
    rotationSensitivity: 0.1,       // Rotation sensitivity
    maxRotationAngle: Math.PI / 2, // Max rotation angle (±90 degrees)
  },

  // Throw power settings
  throw: {
    speedMultiplier: 4,  // Speed multiplier
    minSpeed: 0.8,       // Min speed
    maxSpeed: 2.0,       // Max speed
    upFactor: 1,         // Upward force multiplier
  },

  // Debug settings
  debug: {
    showColliders: false,  // Whether to show collision bounds
  },
} as const;

Usage Example

// Get ball radius
const ballRadius = GameParams.ball.radius;

// Get physics friction coefficient
const friction = GameParams.physics.friction;

// Calculate throw power
const speed = Math.min(
  GameParams.throw.maxSpeed,
  Math.max(
    GameParams.throw.minSpeed,
    power * GameParams.throw.speedMultiplier
  )
);

Iteration workflow

  • Keep params centralized (one place to tweak)
  • Change one dimension at a time and playtest quickly
  • Prefer small, reversible changes to avoid losing track of cause/effect