Summary

A variable is a named container that holds a value the game needs to remember. C# is a statically typed language — every variable must declare its type, and that type cannot change. The four types used throughout Unity game scripting are int, float, bool, and string. Choosing the right type matters: speed can be 2.5 units, but not 2.5 lives.


Key ideas

Core types:

TypeStoresGame examples
intWhole numbersscore, lives, level, health
floatDecimal numbersmoveSpeed, jumpForce, timer
booltrue or falseisAlive, isGameOver, isGrounded
stringTextplayerName, levelName

Can an int hold a decimal? No. An int stores whole numbers only, and C# will not silently throw away the fractional part for you:

int a = 5.5f;              // compile error CS0266: Cannot implicitly convert type 'float' to 'int'.
                           // An explicit conversion exists (are you missing a cast?)
int b = (int)5.9f;         // 5: an explicit cast truncates toward zero (it does not round)
int c = (int)-5.9f;        // -5, not -6: toward zero, not down
int d = 7 / 2;             // 3: int divided by int is integer division
float e = 7 / 2;           // 3 (stored as 3.0): the division happened in ints before the assignment
float f = 7 / 2f;          // 3.5: make either operand a float to get a decimal result
int r = Mathf.RoundToInt(5.6f);   // 6: round to nearest instead of truncating

Microsoft’s C# reference states that there is no implicit conversion from float or double to int, and that when you convert a float or double to an integral type explicitly, “this value is rounded toward zero to the nearest integral value” (Microsoft, Built-in numeric conversions (C# reference)). The other direction is fine: float speed = 5; compiles, because every int converts implicitly to float. In Unity, choose the rounding you mean: Mathf.RoundToInt (nearest; a value ending in exactly .5 goes to the even neighbour, so 2.5f becomes 2), Mathf.FloorToInt (down) or Mathf.CeilToInt (up) (Unity Technologies, Scripting API: Mathf.RoundToInt). Integer division is the usual cause of a health bar stuck at 0: currentHealth / maxHealth with two ints is 0 until health is full, so write (float)currentHealth / maxHealth.

const: A value that is fixed at compile time and can never be modified. Use it for values that must not change, such as maximum health or a fixed damage multiplier.

Access modifiers in Unity:

  • public — accessible from other scripts; also appears in the Inspector.
  • private — accessible only within this script; hidden from the Inspector by default.
  • [SerializeField] — makes a private field visible in the Inspector without making it accessible to other scripts. Preferred over public when Inspector access is needed but external access is not.

[Header("...")]: An Inspector attribute that adds a bold label above a group of fields. Used to organise related variables visually in the Inspector.

Arrays: An ordered collection of values of the same type. Size is fixed at initialisation. Elements are accessed by their zero-based index; the Length property returns the number of elements.

Fields vs local variables: A field is declared at class level, outside any method. It belongs to the object and remembers its value between calls to Start, Update and other methods. A local variable is declared inside a method or block. It exists only while that method or block is running.


In practice

// Basic type declarations
int score = 0;
int lives = 3;
float moveSpeed = 5.5f;   // float literals require the 'f' suffix
bool isAlive = true;
string playerName = "Hero";
 
// const — value cannot be changed anywhere
private const int MaxHealth = 100;
// Inspector-visible fields using public and [SerializeField]
[Header("Player State")]
public int health = 100;          // public — visible in Inspector, accessible to other scripts
 
[SerializeField]
private float jumpForce = 8f;     // private — visible in Inspector, not accessible externally
// Arrays
int[] coinValues = { 10, 25, 10, 50, 15 };   // initialise with values
string[] levelNames = new string[4];          // initialise empty with size
 
// Access by index (zero-based)
int first = coinValues[0];    // 10
int last  = coinValues[4];    // 15
 
// Length property
int count = coinValues.Length;  // 5

Compound assignment operators:

score += 10;   // same as: score = score + 10
score -= 5;    // same as: score = score - 5
health *= 2;   // same as: health = health * 2

Tracing state through a small game rule:

[SerializeField] private int health = 100;  // field: remembered by the object
 
private void TakeDamage(int amount)
{
    int oldHealth = health;     // local variable: exists only during this call
    health -= amount;           // field changes and stays changed
 
    Debug.Log($"Health changed from {oldHealth} to {health}");
}

Read this code in order:

  1. health starts as object state and can be seen in the Inspector.
  2. amount is supplied by whoever calls TakeDamage.
  3. oldHealth temporarily remembers the previous value so the log message can explain the change.
  4. health -= amount changes the field, so the new value remains after the method ends.

Gotchas

  • float literals must have an f suffix: 5.5f not 5.5. Without it, C# treats the value as a double and the assignment will fail to compile.
  • Putting a decimal into an int is a compile error (CS0266), and the explicit (int) cast truncates rather than rounds; (int)0.99f is 0. Use Mathf.RoundToInt when you mean “nearest”.
  • Dividing two ints discards the remainder (7 / 2 is 3, 1 / 2 is 0), even if you store the result in a float. Cast one side to float first.
  • Array indices start at zero. An array of 5 elements has indices 0–4; accessing index 5 throws an IndexOutOfRangeException.
  • Prefer [SerializeField] private over public when you only need Inspector access. Making a field public for Inspector convenience exposes it to unintended external mutation.
  • const fields are implicitly static — they belong to the type, not to an instance. They cannot be modified at runtime and cannot be exposed to the Inspector.
  • A local variable declared inside Update resets every frame. If a timer, score or state flag needs to remember its value, declare it as a field.