1.3 — Your First Script: Variables & Update
Today you write your first C#: one throwaway script, PlayerLogger.cs, that prints to the Console once a second. It teaches the three things every later lesson leans on — how a script attaches to a GameObject, what Start() and Update() do, and where a value has to live to be remembered.
Get a script of my own printing to the Console, and keep printing once a second instead of sixty times, so the Console is still readable a minute later.
First, before any code: cut that into parts small enough that you could look each one up on its own.
- Make the file something Unity will runOne class in the file, its name matching the filename exactly, inheriting
MonoBehaviourso Unity can attach it as a component. Get this wrong and the file never runs.Read: theclass Boss : MonoBehaviourrow in the quick reference. - Put the two values that have to be remembered in the class bodyA
reportDelaytuning knob and anextReportTimedeadline, both declared outside every method: created once and kept for the whole game, where the same lines inside a method would be rebuilt sixty times a second.Read: the declare-vs-assign split in the scope rule below, and the[SerializeField]row in the quick reference. - Give the deadline its first value in the once-only setupThe once-only setup runs before the first frame. It sets
nextReportTimeto the current game time plus the delay, and prints one line so you know the script ran.Read: the once-only setup andTime.timerows in the quick reference. - Gate the print and re-arm the deadlineThe every-frame method runs far too often to print. The
ifasks whether the deadline has passed; if it has, print, then push the deadline a second into the future so the gate closes again.Read: theTime.timerow in the quick reference.
Only the last part does anything you can see. The first three exist because of one question — does anything next frame need to know about this? — asked three times.
Could you look this part up on its own, in one search? "Print to the Console" cannot be looked up; "the seconds since Play" can.
Your turn
Which of the four parts would you have written first if nobody had shown you the split — and what would have gone wrong?
- Project window →
Assets/Scripts, right-click → Create → C# Script, name it PlayerLogger. Unity fills the new file with a class of the same name. - Hierarchy → select the Player, then Inspector → Add Component → search
PlayerLoggerand add it. Dragging the script file onto the object in the Hierarchy does the same thing. - Console window → Window → General → Console. Every log line lands here. Turn on Clear on Play so each run starts empty.
- Game view → press Play and watch the Console count the seconds, then press Play again to stop — stopping wipes the Console too.
- Inspector → with the Player selected, find Player Logger (Script) and change the Report Delay box while the game runs. That box is the private field, tuned without touching code.
The Player's Inspector once step 2 is done. Player Logger (Script)is a component like any other, andReport Delayis the only box it has —[SerializeField]is what put a box on a private field.
The scope rule: a variable declared inside a method is created when the method starts and destroyed when it ends, so it cannot remember anything between calls. One declared in the class body, outside every method, belongs to the object and lives as long as the object does. Declaring says the name exists, assigning gives it a value, and one line may do both only when nothing next frame needs the value.
| API | What it is | What you get | What it costs |
|---|---|---|---|
Start() | Lifecycle hook Unity calls once, before the first frame. | One-time setup: a field's starting value, one line proving the script ran. | It runs once, so it cannot watch anything that changes while the game runs. |
Update() | Lifecycle hook Unity calls every frame — roughly 60 times a second. | Continuous work: polling input, checking a timer, moving something. | Not a fixed rate — a heavy frame changes how often it runs, so nothing inside may assume one. |
Debug.Log(object message) | Prints a value into the Console. | Developer eyes: with no UI yet, the only way to see what the code is doing. | A real side effect — one log inside Update() prints sixty messages a second. |
Time.time | A float: seconds elapsed since you pressed Play. | A clock to compare against: "has my deadline passed?" | It resets when you stop, and keeps counting while paused. |
[SerializeField] private float reportDelay = 1.0f; | A field declaration plus the attribute that serializes it. | A private field with an Inspector box, tunable without touching code. | The box beats the code default once set, and exists only on the object carrying the script. |
Socratic: Lifecycle and Timing
- If your computer runs the game at 120 FPS, how many times will
Update()execute in one second? On a battery-saving laptop at 30 FPS? - Write
Debug.Log("hello")directly insideUpdate()with no check around it: what happens to the Console in 5 seconds? - Ask the scope question about your own script — does anything next frame need to know about this? Which parts of the Break It Down block answer yes, and which answer no?
The question this answers“how do I print text to the console?”
You are editing Assets/Scripts/PlayerLogger.cs. Type these in order — the whole file is under twenty lines.
Step 1Name the file and the class
A C# file holds one class, and the class name has to match the filename exactly — Unity uses the filename to find your class.
// Assets/Scripts/PlayerLogger.cs — a new, empty file you create in the Project window.
using UnityEngine;
public class PlayerLogger : MonoBehaviour
{- 2Tells C# where to find everything Unity provides —
MonoBehaviour,Debug,Time. Without it, those names do not exist. - 4Declares the class. The
: MonoBehaviourpart makes it a component — a script Unity can attach to a GameObject.The name afterclassmust match the filename exactly, capitals included, or Unity throws an error the moment you press Play.
Step 2Put the values that have to be remembered in the class body
Both lines sit inside the class braces but outside any method — the placement the scope rule above is about.
// PlayerLogger.cs — class body: inside the class braces, outside every method.
[SerializeField] private float reportDelay = 1.0f;
private float nextReportTime;- 2Seconds between reports.
[SerializeField]makes a private field editable in the Inspector; thefsuffix marks it as afloat, not adouble. - 3When the next report is allowed.
private: no other script may touch it.It has no value yet. Declaring and assigning are two separate jobs, andStart()is about to do the second one.
Step 3Write Start() — the once-only setup
Start() is a lifecycle method: Unity calls it for you, exactly once, before the first frame.
// PlayerLogger.cs — below the fields, above Update().
void Start()
{
nextReportTime = Time.time + reportDelay;
Debug.Log("PlayerLogger is alive.");
}- 2The method Unity runs once when the object appears.
voidmeans it hands nothing back. - 4The deadline for the first report: current game time plus one second. This is the assignment the declaration above left undone.
- 5Prints one line to the Console so you know the script attached and ran.
Step 4Write Update() — the every-frame gate
Update() runs once per frame — roughly 60 times a second, far too often to print. The if is what keeps the Console readable.
// PlayerLogger.cs — below Start(). Unity calls this once per frame.
void Update()
{
if (Time.time >= nextReportTime)
{
Debug.Log("Player has been running for " + Time.time + " seconds.");
nextReportTime = Time.time + reportDelay;
}
}- 2The method Unity runs every frame, automatically.
- 4The gate: "has enough time passed to report again?"
>=means at or past; the==you might expect would be wrong, because frames are not exact. - 6Prints the message. The
+glues text and number into one string. - 7Moves the deadline a second into the future, so the gate closes again.Delete this line and the Console floods: the gate stays open, so it prints on every frame from then on.
Check your work: the finished filebuild it as you read — then open this to check every line
Do not paste it in — compare it against what you built, and be able to say what any differing line would do instead.
// ------------------------------------------------------------------
// REFERENCE FILE — Assets/Scripts/PlayerLogger.cs
// Written to be read, not pasted. Build it with the walkthrough
// above, one step at a time, then open this and compare.
// If it is sitting in your project unchanged, that is the thing
// to go and do now — the walkthrough is still above you.
// ------------------------------------------------------------------
using UnityEngine;
public class PlayerLogger : MonoBehaviour
{
// ---- class body: every field lives up here, outside every method --------
[SerializeField] private float reportDelay = 1.0f;
private float nextReportTime;
// ---- Start(): Unity calls this once, before the first frame ------------
void Start()
{
nextReportTime = Time.time + reportDelay;
Debug.Log("PlayerLogger is alive.");
}
// ---- Update(): Unity calls this every frame ----------------------------
void Update()
{
if (Time.time >= nextReportTime)
{
Debug.Log("Player has been running for " + Time.time + " seconds.");
nextReportTime = Time.time + reportDelay;
}
}
}Bug Hunt: Break the Logger on Purpose
- Cut
private float nextReportTime;from the class body and declare it inside the method that runs every frame, giving it a starting value there. What does the Console do now? - Rename the class to
playerLogger(lowercase p), leaving the file namedPlayerLogger.cs. What error appears when you press Play? - Attach the script to the Turret instead of the Player. Does it still run, and what does that prove about components and GameObjects?
- Comment out
nextReportTime = Time.time + reportDelay;inside theif. What happens to the Console within 2 seconds, and why does the gate never close again?
Checkpoint — before moving on, you must be able to:
- Create a C# script, attach it to a GameObject, and read its output in the Console
- Explain which lifecycle method runs once and which runs every single frame
- Explain, without looking, why a variable declared inside a method cannot remember anything — and where it should go instead
- Split a declaration from an assignment, and say why you sometimes must
- Use
Time.timeto run something on an interval instead of every frame
Reference Docs
Need a deeper explanation? These pages cover the same concepts:
- Unit 1: Setup & Movement — scripts, variables, input, vectors
- Unity + C# Quick Reference — the API tables
- How do I…? index — every "how do I do X in Unity" question, pointed at the lesson that answers it
Next: 1.4 — Movement I: Input, Direction, Vector, where the scope rule from today becomes a moving character.
Saved on this device only — the Dashboard tracks your progress.