Every chapter before this one taught you general-purpose skills: C, C++, C#, data structures, memory, debugging. None of it was aimed at one specific job title. That is on purpose. Almost nobody starts a game programming career already specialized — studios hiring junior programmers are hiring for general problem-solving ability first, and a specific track second.
Here is what actually happens for most people: your first job title is something broad, like "Programmer" or "Gameplay Programmer," and for the first year or two you get handed a mix of tasks — some UI work, some gameplay logic, maybe a small tool, maybe a bug buried in the network code. Somewhere in that first year or two, a pattern shows up: you keep getting assigned one kind of work, or you keep volunteering for one kind of work, and that becomes your track. This moment is the specialization fork — where "programmer" splits into "gameplay programmer," "graphics programmer," and so on.
The exceptions are graphics, engine, and technical art. These three lean on deep math or pipeline knowledge that is hard to pick up casually on the job, so studios hiring specifically for them often want to see it already — a shader demo, a small custom renderer, a rigging tool — even from a junior candidate. If you already suspect you want one of those three, it helps to start building a small portfolio piece in that direction earlier, alongside the broad foundation, instead of waiting for a studio to hand you the specialization.
A gameplay programmer writes the code that makes the game a game: what happens when the player presses jump, how an ability triggers, how a quest tracks progress, how an inventory item gets used. If a designer can describe a rule, a gameplay programmer is usually the one who turns that rule into working code.
Stand-up in the morning: you pick up a ticket that says "add a dash ability with a cooldown." You spend most of the day inside the Unity or Unreal editor, not a plain text editor — write a bit of code, press play, walk the character around, tweak a number, press play again. Late afternoon a designer walks over and says the dash "feels floaty," and you spend twenty minutes adjusting acceleration curves before it feels right. You commit, open a pull request, and review someone else's ability code before you leave.
A very common early gameplay task: make a jump feel forgiving. If you only let the player jump while a ground-check reports "grounded," a player who presses jump one frame after walking off a ledge gets nothing — and it feels like a bug even though the code is technically correct. The fix is coyote time (named after the cartoon coyote who does not fall until he looks down): a short grace window after leaving the ground where a jump input still works.
using UnityEngine;
public class JumpController : MonoBehaviour {
public float coyoteTime = 0.15f; // seconds of grace after leaving the ground
public float jumpForce = 8f;
private bool isGrounded;
private float timeSinceGrounded;
private Rigidbody rb;
void Start() {
rb = GetComponent<Rigidbody>();
}
void Update() {
isGrounded = CheckGrounded(); // your own ground-check, not shown here
if (isGrounded) {
timeSinceGrounded = 0f;
} else {
timeSinceGrounded += Time.deltaTime;
}
bool canStillJump = timeSinceGrounded <= coyoteTime;
if (Input.GetButtonDown("Jump") && canStillJump) {
rb.linearVelocity = new Vector3(rb.linearVelocity.x, jumpForce, rb.linearVelocity.z);
timeSinceGrounded = coyoteTime + 1f; // used up, block a second jump this window
}
}
bool CheckGrounded() {
return Physics.Raycast(transform.position, Vector3.down, 1.1f);
}
}
Without coyote time, only frame 0 would let the player jump. With it, frames 0 through roughly 4 (up to 0.15s) all still work, matching what players expect their reflexes to do even though they technically left the ground already.
Gameplay programmer has by far the most job listings of any track — nearly every studio, at every size, needs at least a few. It also gets by far the most applicants per listing, because it is the track everyone thinks of first and the one most junior portfolios target. Expect a crowded funnel: getting the interview is often harder than doing well once you are in it.
A graphics programmer (also called a rendering programmer) writes the code that decides how pixels end up on screen: lighting, shadows, reflections, fog, post-processing effects, and how efficiently the GPU (graphics processing unit — the chip that draws the frame) gets used. This is not "make things pretty" in a general sense — it is highly technical, low-level work, closer to engine/systems than to gameplay.
You open a GPU profiler (a tool like RenderDoc or Nsight that shows exactly what the graphics card did, frame by frame) because a level is running at 40fps instead of the target 60fps. You find one shadow-casting light redrawing its entire shadow map every frame when it should only redraw when something in view moves. You fix it, the frame time drops, but a totally unrelated shimmering artifact shows up on a character's hair — so the rest of the afternoon goes to that instead.
A near-universal first graphics task is implementing basic diffuse lighting (also called Lambertian shading, after the physicist who described it): a surface facing the light directly should look bright, a surface facing away should look dark. The math is one dot product between the surface's normal (a vector pointing straight out of the surface) and the direction toward the light.
// Simplified HLSL-style pixel shader
float3 N = normalize(surfaceNormal); // surface normal, pointing outward
float3 L = normalize(lightDirection); // direction FROM surface TOWARD the light
float brightness = max(dot(N, L), 0.0); // negative would mean "facing away"
float3 finalColor = baseColor * brightness;
The dot product between two normalized (length-1) vectors is exactly the cosine of the angle between them — 1.0 when they point the same way, 0.0 at a right angle, negative past that. Clamping negative results to 0 with max stops a surface facing away from the light from somehow being "lit less than nothing," which would make no physical sense.
Fewest total openings of any track covered here, and the hardest to break into as a junior without a demo reel or a small custom renderer to show — studios want proof you can already think in this math, not just a promise you will learn it. The upside: graphics skills transfer almost directly outside games entirely — film and VFX studios, industrial simulation, automotive and robotics visualization, medical imaging, and chip/GPU companies all want people who understand the same rendering pipeline.
An engine programmer (also called a systems programmer) builds the low-level foundations every other programmer on the team relies on without thinking about: memory allocators, the entity/component system, the file and asset loading pipeline, the threading/job system that spreads work across CPU cores. If gameplay programmers build the house, engine programmers pour the foundation and run the plumbing.
You are chasing a memory leak that only shows up after two hours of continuous play, using a heap profiler (a tool that tracks every allocation and where it came from). You find a system that allocates a small object every frame and never frees it. The fix is three lines. The other seven hours went to finding those three lines, and to writing a test that makes sure it cannot silently happen again.
A classic first engine task: replace a naive "allocate a new object every time" pattern with a fixed-size memory pool (a pre-allocated block of memory carved into equal-sized slots, reused instead of returned to the operating system). This is the same allocation-cost idea from the memory chapters, applied to a real, repeated in-game action like spawning bullets.
#include <iostream>
class BulletPool {
public:
BulletPool(int capacity) : capacity(capacity) {
active = new bool[capacity];
for (int i = 0; i < capacity; i++) active[i] = false;
}
int Allocate() {
for (int i = 0; i < capacity; i++) {
if (!active[i]) {
active[i] = true;
return i; // slot index, not a new heap address
}
}
return -1; // pool full, no allocation happened
}
void Free(int slot) {
active[slot] = false;
}
private:
bool* active;
int capacity;
};
int main() {
BulletPool pool(3);
int a = pool.Allocate();
int b = pool.Allocate();
std::cout << "Allocated slots: " << a << ", " << b << std::endl;
pool.Free(a);
int c = pool.Allocate();
std::cout << "Reused slot: " << c << std::endl;
return 0;
}
No new or delete ever runs after setup — every "allocation" is just flipping a boolean in an array that was carved out once. Slot 0 gets reused the moment it is freed, instead of the operating system being asked for fresh memory every single time a bullet fires. At 60 bullets a second, this difference is the gap between a smooth frame rate and stutter.
Fewer openings than gameplay, concentrated at larger studios, engine-focused teams, and middleware companies. Turnover is low — senior engine programmers rarely leave, since the role rewards deep, studio-specific knowledge — so junior openings do exist but are less frequent and often want a candidate who already shows strong C++ fundamentals unprompted.
A tools programmer builds the software other people on the team use to do their jobs faster: custom editor windows inside Unity or Unreal, asset import pipelines, build automation, in-house level editors. The "user" of a tools programmer's code is almost never a player — it is a designer, an artist, or another programmer sitting two desks away.
A designer mentions, half-complaining, that placing 200 collectible items by hand takes an entire afternoon. You spend two hours building a small editor window: click a button, it scatters items along a spline (a curve) with adjustable spacing. You hand it over, the designer uses it in front of you, and the 200-item task takes four minutes instead of an afternoon. That immediate, visible payoff is the defining feeling of this track.
A common first tools task: write an editor script that scans a scene and reports a problem automatically, instead of an artist finding it by accident during a playtest. Here, a script that finds every renderer missing a collider (a common cause of "I can walk through this wall" bugs):
using UnityEngine;
using UnityEditor;
public class MissingColliderChecker {
[MenuItem("Tools/Check Missing Colliders")]
static void CheckScene() {
Renderer[] renderers = Object.FindObjectsByType<Renderer>(FindObjectsSortMode.None);
int missing = 0;
foreach (Renderer r in renderers) {
if (r.GetComponent<Collider>() == null) {
Debug.LogWarning(r.gameObject.name + " has no Collider!", r.gameObject);
missing++;
}
}
Debug.Log("Check complete. " + missing + " object(s) missing a collider out of " + renderers.Length + " checked.");
}
}
This adds a menu item under Tools in the editor, and turns a "walk around and hope you notice" manual check into a one-click, one-second scan. The whole team benefits every time anyone runs it, not just the person who wrote it.
This is genuinely one of the easiest tracks to break into as a junior, and it is undervalued by candidates for exactly that reason — "tools programmer" sounds less exciting than "gameplay programmer" on paper, so fewer people apply, even though the underlying skills overlap heavily with gameplay. A finished tool is also one of the clearest, most demo-able portfolio pieces a junior can show: it is small, it obviously works, and a hiring manager can see the value in ten seconds.
A technical artist (often shortened to "tech artist") sits directly between the art team and the programming team. The job covers whatever falls in the gap between the two: writing shaders artists can use without coding, building and fixing character rigs, setting up animation pipelines, and figuring out why a beautiful visual effect is quietly costing 8 milliseconds of frame time.
Morning: inside Blender or Maya, fixing a character rig where the shoulder pinches at extreme rotation (the candy-wrapper problem from the rigging chapter). Afternoon: writing a small shader so an artist can control a "dissolve" effect with a single slider instead of editing shader code directly. Evening stand-up: explaining to the art director, in plain terms, why a requested effect would cost too much frame time on the target hardware, and proposing a cheaper alternative that looks close enough.
A common first tech-art task is writing a small pipeline script that checks assets follow the team's naming convention before they get imported — catching a mistake in seconds instead of a programmer discovering it later as a mysterious broken reference.
# Python, run inside Maya (using Maya's "cmds" module to talk to the scene)
import maya.cmds as cmds
REQUIRED_PREFIXES = {"joint": "jnt_", "mesh": "geo_"}
def check_naming():
problems = []
for obj in cmds.ls(dag=True, long=False):
obj_type = cmds.objectType(obj)
prefix = REQUIRED_PREFIXES.get(obj_type)
if prefix and not obj.startswith(prefix):
problems.append(obj)
if problems:
print("Naming problems found:")
for p in problems:
print(" " + p)
else:
print("All names follow convention.")
check_naming()
Two objects break convention: Shoulder_L should be jnt_Shoulder_L, and Rock_Big should be geo_Rock_Big. A script like this runs in seconds and catches exactly the kind of small, easy-to-miss mistake that otherwise turns into a confusing bug much later, once an export or import step assumes the naming convention was followed.
High demand relative to how few qualified people exist. The bottleneck is not job openings — it is finding candidates who are genuinely competent at both halves of the job, since most programmers who dabble in art, or artists who dabble in code, stay firmly on one side of that line. If you can honestly do both, even at a junior level, you are rarer than you might think.
An AI programmer writes the decision-making code for non-player characters (NPCs) — enemies, companions, crowds. Despite the name, this is almost never machine learning in the modern "train a neural network" sense; it is mostly finite state machines (a system that is always in exactly one named state, like Idle or Chase, and follows rules for switching between states), behavior trees (a tree of conditions and actions used to pick behavior), and pathfinding.
During a playtest, an enemy stands still while the player shoots it from clearly visible range. You dig into the behavior tree and find a vision-check condition that only fires once a second instead of every frame — a rare timing bug that only shows up when the player approaches from one specific angle. You fix the check, then spend the rest of the day tuning aggro range and reaction delay with a designer, who keeps saying "a little more aggressive" until it feels right.
A near-universal first AI task: a basic three-state enemy — Idle, Chase, Attack — switching state based on distance to the player.
using UnityEngine;
public class EnemyAI : MonoBehaviour {
enum State { Idle, Chase, Attack }
public Transform player;
public float chaseRange = 8f;
public float attackRange = 2f;
State currentState = State.Idle;
void Update() {
float distance = Vector3.Distance(transform.position, player.position);
State newState = currentState;
if (distance <= attackRange) {
newState = State.Attack;
} else if (distance <= chaseRange) {
newState = State.Chase;
} else {
newState = State.Idle;
}
if (newState != currentState) {
Debug.Log("State changed: " + currentState + " -> " + newState + " (distance " + distance.ToString("F1") + ")");
currentState = newState;
}
}
}
The state only logs a change when it actually flips, not every single frame — a small but important detail, since an AI system that reacts every frame regardless of change quickly becomes noisy and wastes work. Everything past this — smarter transitions, cover-seeking, group coordination — builds on exactly this same "check a condition, switch state" skeleton.
Smaller than gameplay overall. Many small and mid-size studios do not have a dedicated "AI programmer" at all — this work gets folded into the general gameplay programmer role. Dedicated AI programmer openings concentrate at larger studios with complex NPC systems (open-world games, tactics/strategy games, large enemy rosters), where the AI code is big enough to need a specialist.
A network programmer makes multiplayer work: keeping multiple players' games in sync, deciding which computer is "allowed" to be right when two players disagree, and building the RPC calls (remote procedure call — a function call that actually runs on a different machine) that let one player's action show up correctly on everyone else's screen. Many network programmers also own the backend — the servers outside the game client itself: accounts, matchmaking, inventory, leaderboards, live events.
A tester reports that on a bad connection, other players' characters "rubber-band" — snap backward suddenly. You trace it to client-side prediction (the client guessing where a character is before the server confirms it) disagreeing too aggressively with the server's authoritative position. You spend the day tuning how corrections smooth in, then switch to writing a backend endpoint that grants a daily login reward, and end the day load-testing it with a script that simulates 500 fake logins at once.
A common first networking task with a framework like Unity's Netcode for GameObjects: make one player's position update correctly on every other player's screen, with the server as the authority (the single source of truth, so players cannot just teleport by editing their own local position).
using Unity.Netcode;
using UnityEngine;
public class NetworkedPlayer : NetworkBehaviour {
// NetworkVariable: a value the server owns, and Netcode
// automatically replicates to every connected client
NetworkVariable<Vector3> networkPosition = new NetworkVariable<Vector3>();
void Update() {
if (IsOwner) {
// Only the player who owns this character asks the server to move it
Vector3 input = new Vector3(Input.GetAxis("Horizontal"), 0, Input.GetAxis("Vertical"));
RequestMoveServerRpc(transform.position + input * Time.deltaTime * 5f);
}
// Every client, including the owner, renders from the replicated value
transform.position = networkPosition.Value;
}
[ServerRpc]
void RequestMoveServerRpc(Vector3 requestedPosition) {
// The server decides the real position (could reject/clamp it here)
networkPosition.Value = requestedPosition;
}
}
Client A never sets its own position directly — it only requests a move. The server is the only machine that actually writes to networkPosition. This one-directional flow (client asks, server decides, everyone renders the server's answer) is the core idea the rest of networking builds on, including harder problems like handling a player who requests an impossible move.
Fewer openings than gameplay, but steady — every multiplayer or live-service game needs at least one network programmer, and that category of game is not shrinking. Genuinely hard to enter as a junior: networking bugs are often non-deterministic (they do not reproduce the same way twice) and require experience to reason about efficiently, so this track leans especially hard on mentorship early on.
A UI programmer builds the menus, the HUD (heads-up display — the on-screen overlay showing health, ammo, minimap, and similar), inventory screens, and settings menus, and writes the code layer that connects those visuals to the actual game data and events behind them.
You wire up a new shield bar so it fills and drains correctly alongside the existing health bar. QA reports the shield bar overlaps the health bar on ultra-wide monitors — an anchoring bug that only shows up at certain aspect ratios. You fix the layout, then spend an hour making sure a controller can navigate the inventory grid the same way a mouse can, since both need to work identically.
A common first UI task: make a health bar respond to a damage event instead of being checked and redrawn every single frame (which wastes work and is harder to keep in sync).
using UnityEngine;
using UnityEngine.UI;
public class HealthBarUI : MonoBehaviour {
public Image fillImage; // the bar's fill graphic, Image Type = Filled
public PlayerHealth health; // has an event: OnDamage(int currentHp, int maxHp)
void OnEnable() {
health.OnDamage += HandleDamage;
}
void OnDisable() {
health.OnDamage -= HandleDamage;
}
void HandleDamage(int currentHp, int maxHp) {
float fraction = (float)currentHp / maxHp;
fillImage.fillAmount = fraction;
Debug.Log("Health bar updated: " + currentHp + "/" + maxHp + " (" + (fraction * 100f).ToString("F0") + "%)");
}
}
The bar only does work when OnDamage actually fires, subscribing and unsubscribing cleanly in OnEnable/OnDisable so it does not keep listening after the object is gone. This event-driven pattern — react to a change, do not poll for one every frame — is the backbone of almost all UI programming.
Steady demand — every game ships with a UI, so the work never disappears — but at smaller studios it is usually folded into general gameplay work rather than being its own listed role. Where a dedicated UI programmer role does exist, it tends to be less crowded than a generic "gameplay programmer" posting, simply because it sounds less glamorous and fewer junior applicants target it directly.
An audio programmer connects sound effects and music to game events, builds mixing logic (deciding how loud different sounds should be relative to each other, and when), and integrates audio middleware (a third-party toolkit — Wwise and FMOD are the two most common — that sound designers use to build interactive audio without needing a programmer for every change).
A sound designer reports a footstep sound plays twice on concrete but never on grass. You trace it to an animation event firing twice per footstep in one animation clip but only once in another — an animation bug wearing an audio costume. Afternoon: implementing music ducking (automatically lowering music volume while a character is speaking) so dialogue stays audible without a sound designer manually riding the volume fader for every line.
A common first audio task: implement music ducking so background music automatically quiets while dialogue plays, and recovers when it stops.
using UnityEngine;
public class MusicDucker : MonoBehaviour {
public AudioSource music;
public AudioSource dialogue;
public float duckedVolume = 0.25f;
public float normalVolume = 1.0f;
public float duckSpeed = 2f; // volume change per second
void Update() {
float target = dialogue.isPlaying ? duckedVolume : normalVolume;
music.volume = Mathf.MoveTowards(music.volume, target, duckSpeed * Time.deltaTime);
}
}
Mathf.MoveTowards slides the volume smoothly toward whichever target is currently correct, instead of snapping instantly — an instant volume jump is very noticeable and sounds like a mistake, while a smooth half-second slide reads as intentional mixing.
The smallest dedicated headcount of any track in this chapter. Many studios have zero dedicated audio programmers — a sound designer handles simple scripting themselves, or the work gets folded into gameplay/tools programming. The few dedicated openings that exist are concentrated at larger studios and at the middleware companies themselves (Audiokinetic, Firelight), and are competitive precisely because there are so few seats.
Put side by side, the nine tracks split along a few consistent axes: how much math the daily work leans on, which language dominates, roughly how many jobs exist for it, and how hard it typically is to land as a junior. None of these numbers are exact — they vary by studio and region — but the relative ordering holds up consistently across the industry.
The numbers above hide a few things that matter more than the raw counts:
Reading descriptions only gets you so far — almost every track sounds appealing in a paragraph. The actual test is building one small thing in each track and paying attention to a very specific signal: not "did I finish it," but did I keep thinking about it after I closed the laptop. That second signal is far more honest than how much you enjoyed the two hours you spent on it, because plenty of tasks are pleasant in the moment and instantly forgettable, while a few are mildly frustrating in the moment yet pull your attention back that evening anyway.
Each of these is small enough to attempt in a weekend using only what earlier chapters already taught you, plus whatever specific engine API you look up as you go:
After trying a few of the nine, these questions tend to sort people quickly. Being honest matters more than answering "yes" to the ones that sound impressive:
Most people check boxes across two or three tracks, not just one — that overlap is normal and useful information, not a failure to find a single "right" answer.
The same track title can mean a noticeably different job depending on what kind of studio you land at. Three broad shapes cover most of the industry:
A mobile live-service studio (the kind HoYoverse-style games are built by) leans hard on tools and network/backend programmers, because shipping a new event every few weeks only works if non-programmers can assemble it themselves through good tools, and because a live game's economy, accounts, and events all run through backend systems. UI programming load is also heavy — banners, event screens, and menus rotate constantly.
A console/PC AAA studio leans hard on graphics and engine programmers, because visual fidelity over a multi-year production is often the actual product being sold, and the team is usually large enough to keep tracks sharply separated — moving from one track to another inside the same studio can take longer here than at a smaller one.
An indie studio barely has separate tracks at all. A team of three or four programmers means everyone does a bit of gameplay, a bit of tools, sometimes a bit of graphics, out of necessity rather than choice. Less specialization is possible, but the flip side is broader ownership and faster, more direct feedback from your own work.
The broad foundation from the earlier chapters is exactly what makes that kind of move possible later. The C++, C#, and data structures skills underneath every track in this chapter are the same skills — just aimed in a different direction depending on which track you are in this year.
"We are looking for a Programmer with a strong grasp of 3D math,
experience with shader authoring, and familiarity with GPU profiling
tools. You will optimize our lighting pipeline and reduce frame time
on target hardware."
Which track from this chapter is this ad for? Rewrite the two requirement sentences in plain, beginner-friendly words.
This is a graphics/rendering programmer ad. Three clues point to it directly: "3D math" (linear algebra), "shader authoring" (writing HLSL/GLSL code), and "GPU profiling tools" (RenderDoc/Nsight-style tools), all named explicitly in section 3's "What You Must Be Strong At" list.
In plain words: "You need to be comfortable with vectors and matrices, and you can write small programs (shaders) that run on the graphics card. You know how to use a tool that shows exactly how long each part of a frame takes on the GPU, so you can make the game run faster without making it look worse."
Best fits: Engine/Systems and Tools. Engine/Systems is built directly on memory and pointer material and rewards deterministic, measurable correctness rather than subjective "feel" tuning — exactly what the candidate says they enjoy and want. Tools also rewards a clear "does it work or not" kind of correctness, involves little of the ambiguous player-feel feedback loop gameplay programmers deal with daily, and does not require a trained art eye.
Worst fit: Technical Artist. Section 6 lists "a real, trained eye for visual quality" as a non-optional requirement, which the candidate explicitly says they lack, and a large part of the job is judged by subjective "does this look right" feedback — close to the kind of ambiguous feedback the candidate says frustrates them.
// Simplified Netcode-style server RPC
[ServerRpc]
void RequestFireServerRpc(Vector3 origin, Vector3 direction) {
if (Physics.Raycast(origin, direction, out RaycastHit hit, 100f)) {
ApplyDamageClientRpc(hit.collider.gameObject.GetComponent<NetworkObject>().NetworkObjectId, 10);
}
}
Name the track, and give two specific reasons drawn from this chapter's "What You Must Be Strong At" list for that track.This belongs to the network/backend programmer. Two reasons, both straight from section 8:
1. It is written as a [ServerRpc] — code that only ever runs on the server, matching the "server as the authority" pattern from section 8's junior task, where the server decides the real outcome and clients only ask.
2. Deciding whether a shot actually hits, and only then telling every client to apply damage, requires exactly the "careful reasoning about state and timing" bullet from section 8's "What You Must Be Strong At" list — the server has to be the one source of truth, or two players could see two different outcomes for the same shot.
(A gameplay programmer might write the raw damage numbers or the raycast logic in a single-player context, but the moment it is wrapped in a ServerRpc/ClientRpc pair, ownership belongs to whoever is responsible for keeping every player's game in agreement.)