18.3 เลือกสายเฉพาะทางของคุณ

เฟส 18 · พอร์ตโฟลิโอ, อาชีพ และสัมภาษณ์ · เวลาเรียน: —

เปลี่ยนสายต่าง ๆ ในหลักสูตรนี้เป็นทิศทางอาชีพเดียวที่ชัด ตรงกับจุดแข็งและสตูดิโอเป้าหมายของคุณ

1. เรียนกว้างก่อน แล้วค่อยเลือก Specialize ทีหลัง

ทุกบทก่อนหน้านี้สอน skill แบบทั่วไปให้เรา: C, C++, C#, data structures, memory, debugging ไม่มีบทไหนเล็งไปที่ตำแหน่งงานใดตำแหน่งงานหนึ่งโดยเฉพาะเลย ตั้งใจแบบนั้นจริง ๆ แทบไม่มีใครเริ่มอาชีพ game programmer แบบ specialize มาตั้งแต่วันแรกหรอก studio ที่รับ junior programmer เขามองหาความสามารถแก้ปัญหาทั่วไป (general problem-solving) เป็นอันดับแรก ส่วนสายเฉพาะทาง (track) มาทีหลัง

สิ่งที่เกิดขึ้นจริงกับคนส่วนใหญ่คือแบบนี้: ตำแหน่งงานแรกของเรามักกว้าง ๆ อย่าง "Programmer" หรือ "Gameplay Programmer" แล้วในปีสองปีแรกเราจะได้งานหลากหลายแบบปนกันไป — บ้างก็งาน UI บ้างก็ gameplay logic บ้างก็ tool เล็ก ๆ บ้างก็บั๊กที่ซ่อนอยู่ใน network code สักพักในช่วงปีสองปีแรกนั้น จะเริ่มเห็น pattern ว่าเรามักได้รับมอบหมายงานแบบใดแบบหนึ่งซ้ำ ๆ หรือเราอาสาทำงานแบบใดแบบหนึ่งซ้ำ ๆ เอง แล้วนั่นแหละจะค่อย ๆ กลายเป็น track ของเรา ช่วงเวลานี้เรียกว่า specialization fork — จุดที่ "programmer" แตกออกเป็น "gameplay programmer", "graphics programmer" และอื่น ๆ

Your first 1-3 years (broad, general tasks) | | Foundations: C, C++, C#, | data structures, debugging | (everything before this chapter) v +-------------------------+ | Junior Programmer | | (title is often just | | "Programmer" or | | "Gameplay Programmer") | +-------------------------+ | the SPECIALIZATION FORK (usually happens gradually, not on day one) | +---------+---------+---------+---------+---------+---------+ | | | | | | | v v v v v v v Gameplay Graphics Engine Tools Tech Art AI Network ...

ข้อยกเว้นคือ graphics, engine และ technical art สามสายนี้พึ่งพา math เชิงลึกหรือความรู้เรื่อง pipeline ที่หยิบมาเรียนรู้เอาระหว่างทำงานแบบสบาย ๆ ได้ยาก studio ที่รับคนเข้าสายพวกนี้โดยเฉพาะเลยมักอยากเห็นฝีมือมาก่อนแล้ว — เช่น shader demo, custom renderer เล็ก ๆ, tool สำหรับ rigging — แม้แต่กับผู้สมัครระดับ junior ถ้าเรารู้ตัวอยู่แล้วว่าอยากไปสายไหนใน 3 สายนี้ การเริ่มสร้าง portfolio ชิ้นเล็ก ๆ ไปในทิศทางนั้นตั้งแต่เนิ่น ๆ ควบคู่ไปกับพื้นฐานกว้าง ๆ จะช่วยได้มาก แทนที่จะรอให้ studio เป็นคนกำหนด specialization ให้เรา

Tip "เรียนกว้างก่อน" ไม่ได้แปลว่า "ไม่มีจุดโฟกัส" มันหมายถึง: สร้างของชิ้นเล็ก ๆ ที่ทำเสร็จจริงในหลากหลายประเภทงานไปเรื่อย ๆ แล้วสังเกตว่าเรามักเลือกทำแบบไหนซ้ำ ๆ เวลาที่ไม่มีใครมาสั่งให้ทำอะไร หัวข้อ 12 จะเปลี่ยนเรื่องนี้ให้เป็นแบบทดสอบจริง ๆ ที่ลองทำกับตัวเองได้

2. Gameplay Programmer

Gameplay programmer คือคนที่เขียนโค้ดที่ทำให้เกมเป็นเกม: เกิดอะไรขึ้นตอนกดปุ่มกระโดด ability ทำงานยังไง quest ติดตามความคืบหน้ายังไง item ใน inventory ถูกใช้ยังไง ถ้า designer อธิบายกฎของเกมออกมาได้ gameplay programmer มักเป็นคนที่แปลงกฎนั้นให้กลายเป็นโค้ดที่ทำงานได้จริง

A Day in the Life

stand-up ตอนเช้า: เราหยิบ ticket ที่เขียนว่า "เพิ่ม dash ability พร้อม cooldown" ส่วนใหญ่ของวันเราจะอยู่ใน editor ของ Unity หรือ Unreal ไม่ใช่แค่ text editor ธรรมดา — เขียนโค้ดนิดหน่อย กด play เดินตัวละครไปมา ปรับตัวเลข กด play ใหม่อีกรอบ ช่วงบ่ายแก่ ๆ designer เดินมาบอกว่า dash "รู้สึกลอย ๆ" (floaty) เราก็ใช้เวลายี่สิบนาทีปรับ acceleration curve จนรู้สึกโอเค แล้ว commit เปิด pull request แล้ว review โค้ด ability ของคนอื่นก่อนเลิกงาน

What You Must Be Strong At

A Typical Junior Task

งาน gameplay งานแรก ๆ ที่เจอบ่อยมาก: ทำให้การกระโดดรู้สึก "ให้อภัย" มากขึ้น ถ้าเราให้ผู้เล่นกระโดดได้แค่ตอนที่ ground-check บอกว่า "grounded" (แตะพื้นอยู่) เท่านั้น ผู้เล่นที่กดกระโดดหลังเดินตกขอบไปแค่เฟรมเดียวจะไม่ได้อะไรเลย — และมันรู้สึกเหมือนบั๊ก ทั้งที่โค้ดถูกต้องตามหลักการแล้ว ทางแก้คือ coyote time (ตั้งชื่อตามการ์ตูนโคโยตี้ที่จะไม่ตกจนกว่าจะมองลงไปข้างล่าง): ช่วงเวลาผ่อนปรนสั้น ๆ หลังออกจากพื้นที่ยังกดกระโดดได้อยู่

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);
    }
}
Frame-by-frame trace, coyoteTime = 0.15s, player walks off a ledge at frame 0: frame time since grounded canStillJump player presses Jump? 0 0.00s yes no 1 0.03s yes no 2 0.06s yes yes -> JUMP WORKS 3 0.10s yes - 6 0.20s NO yes -> jump ignored, too late

ถ้าไม่มี coyote time จะมีแค่เฟรม 0 เท่านั้นที่กระโดดได้ พอมีแล้ว เฟรม 0 ถึงประมาณเฟรม 4 (จนถึง 0.15 วินาที) ยังกระโดดได้หมด ซึ่งตรงกับสิ่งที่ผู้เล่นคาดหวังจากปฏิกิริยาตอบสนองของตัวเอง ทั้งที่จริง ๆ แล้วเขาออกจากพื้นไปแล้ว

Job ad says: In plain words: "Experience implementing responsive "You can make controls feel good, and satisfying player controls" not just technically correct." "Comfortable iterating quickly based "You can take fuzzy feedback like on design and playtest feedback" 'it feels floaty' and turn it into a specific code change."

Job Market

Gameplay programmer มีตำแหน่งงานเปิดรับมากกว่าสายอื่นทุกสายอย่างชัดเจน — แทบทุก studio ทุกขนาดต้องการอย่างน้อยสองสามคน แต่ก็มีคนสมัครต่อตำแหน่งมากที่สุดเช่นกัน เพราะเป็นสายที่ทุกคนนึกถึงเป็นอันดับแรก และเป็น track ที่ portfolio ของ junior ส่วนใหญ่เล็งไปหา เตรียมใจไว้ว่าช่องทางจะแน่นมาก — บางทีการได้สัมภาษณ์ยากกว่าการทำผลงานให้ดีตอนสัมภาษณ์เสียอีก

3. Graphics / Rendering Programmer

Graphics programmer (เรียกอีกอย่างว่า rendering programmer) เขียนโค้ดที่กำหนดว่าพิกเซลบนหน้าจอจะออกมาเป็นยังไง: lighting, shadow, reflection, fog, post-processing effect และ GPU (graphics processing unit — ชิปที่วาดเฟรมภาพ) ถูกใช้งานอย่างมีประสิทธิภาพแค่ไหน นี่ไม่ใช่ "ทำให้สวย" แบบทั่ว ๆ ไป แต่เป็นงานเทคนิคขั้นสูงระดับ low-level ใกล้เคียงกับสาย engine/systems มากกว่า gameplay

A Day in the Life

เราเปิด GPU profiler (เครื่องมืออย่าง RenderDoc หรือ Nsight ที่โชว์ว่าการ์ดจอทำอะไรบ้างในแต่ละเฟรมแบบละเอียด) เพราะด่านหนึ่งรันที่ 40fps แทนที่จะเป็นเป้าหมาย 60fps เราเจอว่าไฟที่ทอดเงาดวงหนึ่งวาด shadow map ใหม่ทั้งอันทุกเฟรม ทั้งที่ควรวาดใหม่แค่ตอนมีอะไรขยับในมุมมองเท่านั้น เราแก้ไข frame time ก็ลดลง แต่ดันมี artifact แบบระยิบระยับที่ผมตัวละครโผล่ขึ้นมาแบบไม่เกี่ยวกัน — บ่ายที่เหลือเลยหมดไปกับเรื่องนั้นแทน

What You Must Be Strong At

A Typical Junior Task

งาน graphics งานแรกที่พบแทบทุกที่คือการทำ diffuse lighting เบื้องต้น (เรียกอีกอย่างว่า Lambertian shading ตามชื่อนักฟิสิกส์ผู้อธิบายมันไว้): พื้นผิวที่หันเข้าหาแสงตรง ๆ ควรดูสว่าง พื้นผิวที่หันหนีแสงควรดูมืด คณิตศาสตร์ที่ใช้คือ dot product หนึ่งครั้งระหว่าง normal ของพื้นผิว (vector ที่ชี้ออกจากพื้นผิวตรง ๆ) กับทิศทางไปหาแสง

// 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;
Worked trace, three sample surface normals, light coming from straight above: N = (0, 1, 0) (surface facing straight up, toward light) dot(N, L) = 1.0 -> brightness = 1.0 (fully lit) N = (0.7, 0.7, 0) (surface facing up and sideways, 45 degrees) dot(N, L) = 0.7 -> brightness = 0.7 (partially lit) N = (0, -1, 0) (surface facing straight down, away from light) dot(N, L) = -1.0 -> max(-1.0, 0.0) = 0.0 (fully dark)

dot product ระหว่าง vector ที่ normalize แล้ว (ยาว 1) สอง vector คือค่า cosine ของมุมระหว่างสอง vector นั้นพอดี — 1.0 ตอนชี้ทางเดียวกัน 0.0 ตอนตั้งฉากกัน แล้วติดลบไปเรื่อย ๆ หลังจากนั้น การ clamp ค่าติดลบให้เป็น 0 ด้วย max ป้องกันไม่ให้พื้นผิวที่หันหนีแสงกลายเป็น "สว่างน้อยกว่าศูนย์" ซึ่งไม่มีเหตุผลทางฟิสิกส์เลย

Job Market

ตำแหน่งงานเปิดรับน้อยที่สุดในบรรดาทุกสายในบทนี้ และยากที่สุดสำหรับ junior ที่จะเข้าไปได้โดยไม่มี demo reel หรือ custom renderer เล็ก ๆ ให้ดู — studio อยากเห็นหลักฐานว่าเราคิดแบบนี้เป็นอยู่แล้ว ไม่ใช่แค่สัญญาว่าจะไปเรียนรู้เอาทีหลัง ข้อดีคือ skill ด้าน graphics ย้ายไปใช้นอกวงการเกมได้เกือบตรง ๆ เลย — studio ภาพยนตร์และ VFX, simulation อุตสาหกรรม, automotive และ robotics visualization, medical imaging, บริษัทชิป/GPU ล้วนอยากได้คนที่เข้าใจ rendering pipeline แบบเดียวกันนี้

4. Engine / Systems Programmer

Engine programmer (เรียกอีกอย่างว่า systems programmer) สร้างรากฐานระดับ low-level ที่ programmer คนอื่นทุกคนในทีมพึ่งพาโดยไม่ต้องคิดถึงมันเลย: memory allocator, ระบบ entity/component, pipeline การโหลดไฟล์และ asset, ระบบ threading/job ที่กระจายงานไปตาม CPU core ถ้า gameplay programmer สร้างบ้าน engine programmer ก็คือคนเทฐานรากและวางระบบท่อน้ำ

A Day in the Life

เรากำลังไล่ตาม memory leak ที่โผล่มาให้เห็นก็ต่อเมื่อเล่นต่อเนื่องไปสองชั่วโมงเท่านั้น โดยใช้ heap profiler (เครื่องมือที่ติดตามทุก allocation และที่มาของมัน) เราเจอระบบหนึ่งที่ allocate object เล็ก ๆ ทุกเฟรมแล้วไม่เคย free เลย ทางแก้คือสามบรรทัด อีกเจ็ดชั่วโมงที่เหลือหมดไปกับการหาสามบรรทัดนั้น และเขียน test เพื่อให้แน่ใจว่ามันจะไม่เกิดขึ้นแบบเงียบ ๆ อีก

What You Must Be Strong At

A Typical Junior Task

งาน engine งานแรกแบบคลาสสิก: แทนที่ pattern แบบไร้เดียงสาอย่าง "allocate object ใหม่ทุกครั้ง" ด้วย fixed-size memory pool (บล็อก memory ที่ allocate ไว้ล่วงหน้าและแบ่งเป็นช่องขนาดเท่ากัน นำกลับมาใช้ซ้ำแทนที่จะคืนให้ operating system) นี่คือแนวคิดเรื่องต้นทุนการ allocate แบบเดียวกับในบท memory เอามาใช้กับ action ที่เกิดซ้ำ ๆ จริงในเกมอย่างการยิงกระสุน

#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;
}
Output: Allocated slots: 0, 1 Reused slot: 0

ไม่มี new หรือ delete รันเลยหลังจาก setup — ทุก "allocation" คือแค่การพลิกค่า boolean ในอาเรย์ที่ถูกจองไว้ครั้งเดียวเท่านั้น slot 0 ถูกนำกลับมาใช้ทันทีที่ถูก free แทนที่จะขอ memory ใหม่จาก operating system ทุกครั้งที่ยิงกระสุน ที่ 60 นัดต่อวินาที ความต่างตรงนี้คือช่องว่างระหว่าง frame rate ที่ลื่นไหลกับอาการกระตุก

Job Market

ตำแหน่งงานเปิดรับน้อยกว่า gameplay กระจุกตัวอยู่ที่ studio ใหญ่ ทีมที่โฟกัส engine โดยเฉพาะ และบริษัท middleware turnover ต่ำ — engine programmer ระดับ senior แทบไม่ค่อยลาออก เพราะบทบาทนี้ให้ผลตอบแทนกับความรู้เชิงลึกที่ผูกกับ studio นั้น ๆ ตำแหน่ง junior จึงมีอยู่บ้างแต่ไม่บ่อย และมักอยากได้คนที่แสดงพื้นฐาน C++ ที่แข็งแรงออกมาเองโดยไม่ต้องถาม

5. Tools Programmer

Tools programmer สร้างซอฟต์แวร์ที่คนอื่นในทีมใช้ทำงานได้เร็วขึ้น: editor window แบบ custom ใน Unity หรือ Unreal, asset import pipeline, build automation, level editor ที่ทำใช้เองภายในทีม "ผู้ใช้" ของโค้ดที่ tools programmer เขียนแทบไม่ใช่ผู้เล่นเลย แต่เป็น designer, artist หรือ programmer คนอื่นที่นั่งโต๊ะห่างไปสองโต๊ะ

A Day in the Life

designer บ่นแบบครึ่งจริงครึ่งเล่นว่าการวาง collectible item 200 ชิ้นด้วยมือกินเวลาไปทั้งบ่าย เราใช้เวลาสองชั่วโมงสร้าง editor window เล็ก ๆ: กดปุ่มเดียว มันจะกระจาย item ไปตาม spline (เส้นโค้ง) พร้อมปรับระยะห่างได้ เราส่งมอบให้ designer ใช้ต่อหน้าเราเลย งาน 200 ชิ้นที่เคยกินเวลาทั้งบ่ายเหลือแค่สี่นาที ผลตอบแทนที่เห็นได้ทันทีแบบนี้แหละคือความรู้สึกที่นิยามสายนี้ได้ดีที่สุด

What You Must Be Strong At

A Typical Junior Task

งาน tools งานแรกที่พบบ่อย: เขียน editor script ที่สแกน scene แล้วรายงานปัญหาให้อัตโนมัติ แทนที่จะให้ artist มาเจอเอาโดยบังเอิญตอน playtest ตัวอย่างนี้คือ script ที่หา renderer ทุกตัวที่ไม่มี collider (สาเหตุยอดฮิตของบั๊ก "เดินทะลุกำแพงนี้ได้"):

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.");
    }
}
Output (Console window), scene with 40 renderers, 3 missing colliders: Rock_04 has no Collider! Fence_Broken_02 has no Collider! Barrel_Stack_01 has no Collider! Check complete. 3 object(s) missing a collider out of 40 checked.

ตัวนี้เพิ่ม menu item ใต้ Tools ใน editor และเปลี่ยนการเช็คแบบ "เดินดูเองแล้วหวังว่าจะสังเกตเห็น" ให้กลายเป็นการสแกนคลิกเดียวจบภายในหนึ่งวินาที ทั้งทีมได้ประโยชน์ทุกครั้งที่ใครก็ตามรัน ไม่ใช่แค่คนที่เขียนมันขึ้นมา

Job ad says: In plain words: "Build internal tooling to improve "Write small programs that make team velocity and workflow" your coworkers' jobs faster." "Strong communication skills, works "You can talk to a non-programmer, closely with designers and artists" understand what they actually need, and not just what they asked for."

Job Market

นี่คือหนึ่งใน track ที่เข้าง่ายที่สุดสำหรับ junior จริง ๆ และมันถูกมองข้ามคุณค่าโดยผู้สมัครเองด้วยเหตุผลนี้แหละ — "tools programmer" ฟังดูน่าตื่นเต้นน้อยกว่า "gameplay programmer" บนกระดาษ คนสมัครเลยน้อยกว่า ทั้งที่ skill เบื้องหลังทับซ้อนกับ gameplay เยอะมาก tool ที่ทำเสร็จแล้วยังเป็นหนึ่งใน portfolio piece ที่ demo ได้ชัดเจนที่สุดที่ junior คนหนึ่งจะมีได้: มันเล็ก มันทำงานได้จริงเห็นชัด ๆ และ hiring manager เห็นคุณค่าได้ภายในสิบวินาที

6. Technical Artist

Technical artist (มักเรียกสั้น ๆ ว่า "tech artist") อยู่ตรงกลางระหว่างทีม art กับทีม programming งานของเขาครอบคลุมทุกอย่างที่ตกอยู่ในช่องว่างระหว่างสองฝั่งนี้: เขียน shader ให้ artist ใช้ได้โดยไม่ต้องเขียนโค้ดเอง สร้างและแก้ character rig ตั้งค่า animation pipeline และหาสาเหตุว่าทำไม visual effect ที่สวยงามชิ้นหนึ่งถึงแอบกิน frame time ไป 8 มิลลิวินาที

A Day in the Life

เช้า: อยู่ใน Blender หรือ Maya แก้ character rig ที่ไหล่บีบตัวตอนหมุนสุดมุม (ปัญหา candy-wrapper จากบท rigging) บ่าย: เขียน shader เล็ก ๆ ให้ artist คุม effect "dissolve" ได้ด้วย slider ตัวเดียว แทนที่จะต้องแก้โค้ด shader เอง stand-up ตอนเย็น: อธิบายให้ art director ฟังด้วยภาษาง่าย ๆ ว่าทำไม effect ที่ขอมาถึงกิน frame time เกินไปบน hardware เป้าหมาย แล้วเสนอทางเลือกที่ถูกกว่าแต่ยังดูใกล้เคียงพอ

What You Must Be Strong At

A Typical Junior Task

งาน tech-art งานแรกที่พบบ่อย คือเขียน pipeline script เล็ก ๆ ที่เช็คว่า asset ทำตามข้อกำหนดการตั้งชื่อของทีมก่อนถูก import — จับข้อผิดพลาดได้ภายในไม่กี่วินาที แทนที่จะให้ programmer มาเจอทีหลังในรูปแบบของ 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()
Output, scene with one badly-named joint and one badly-named mesh: Naming problems found: Shoulder_L Rock_Big

มี object สองตัวที่ผิดข้อกำหนด: Shoulder_L ควรเป็น jnt_Shoulder_L และ Rock_Big ควรเป็น geo_Rock_Big script แบบนี้รันเสร็จภายในไม่กี่วินาทีและจับข้อผิดพลาดเล็ก ๆ ที่มองข้ามง่ายแบบนี้ได้พอดี ซึ่งถ้าไม่จับตอนนี้มันจะกลายเป็นบั๊กที่งงงวยในภายหลัง ตอนที่ขั้นตอน export หรือ import สมมติไปแล้วว่าทุกอย่างตั้งชื่อตามข้อกำหนด

Tip Technical art คือ track ที่คำว่า "ฉันเก่งทั้ง art และ code" เลิกเป็นแค่ประโยคเขียนขายของในเรซูเม่ แล้วกลายเป็นข้อกำหนดของงานจริง ๆ ถ้าเราตรงกับคำอธิบายนี้จริง ๆ track นี้คุ้มค่าที่จะพิจารณาอย่างจริงจัง เพราะผู้สมัครส่วนใหญ่มักเก่งไปทางใดทางหนึ่งอย่างชัดเจน และ studio ก็รู้สึกถึงความขาดแคลนนี้อยู่ตลอด

Job Market

ความต้องการสูงเมื่อเทียบกับจำนวนคนที่มีคุณสมบัติจริง ๆ คอขวดไม่ใช่ตำแหน่งงานที่เปิดรับ — แต่คือการหาคนที่เก่งจริงทั้งสองด้านของงาน เพราะ programmer ส่วนใหญ่ที่แตะ ๆ งาน art หรือ artist ที่แตะ ๆ งาน code มักอยู่ฝั่งใดฝั่งหนึ่งอย่างมั่นคง ถ้าเราทำได้ทั้งสองด้านจริง ๆ แม้แค่ระดับ junior เราก็หายากกว่าที่คิดไว้มาก

7. AI Programmer

AI programmer เขียนโค้ดตัดสินใจให้ non-player character (NPC) — ศัตรู เพื่อนร่วมทาง ฝูงชน ทั้งที่ชื่อสายนี้มีคำว่า AI แต่แทบไม่ใช่ machine learning แบบ "เทรน neural network" ในความหมายสมัยใหม่เลย ส่วนใหญ่เป็น finite state machine (ระบบที่อยู่ในสถานะที่มีชื่อเรียกได้เพียงหนึ่งสถานะเสมอ อย่าง Idle หรือ Chase แล้วมีกฎกำหนดการสลับสถานะ), behavior tree (tree ของเงื่อนไขกับ action ที่ใช้เลือกพฤติกรรม) และ pathfinding

A Day in the Life

ระหว่าง playtest ศัตรูตัวหนึ่งยืนนิ่งอยู่กับที่ทั้งที่ผู้เล่นยิงมันจากระยะที่มองเห็นชัดเจน เราเจาะลึกเข้าไปใน behavior tree แล้วเจอเงื่อนไข vision-check ที่ทำงานแค่วินาทีละครั้งแทนที่จะเป็นทุกเฟรม — บั๊กเรื่อง timing ที่หายากมากและโผล่มาเฉพาะตอนผู้เล่นเข้าใกล้จากมุมใดมุมหนึ่งเท่านั้น เราแก้เงื่อนไขนั้น แล้วใช้เวลาที่เหลือของวันปรับ aggro range กับ reaction delay ร่วมกับ designer ที่พูดซ้ำ ๆ ว่า "ดุขึ้นอีกนิด" จนกว่าจะรู้สึกโอเค

What You Must Be Strong At

A Typical Junior Task

งาน AI งานแรกที่พบแทบทุกที่: ศัตรูสามสถานะพื้นฐาน — Idle, Chase, Attack — สลับสถานะตามระยะห่างจากผู้เล่น

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;
        }
    }
}
Trace as the player walks toward a stationary enemy (chaseRange=8, attackRange=2): distance = 12.0 state = Idle (no change, starts here) distance = 7.5 -> State changed: Idle -> Chase (distance 7.5) distance = 1.8 -> State changed: Chase -> Attack (distance 1.8)

state จะ log การเปลี่ยนแปลงก็ต่อเมื่อมันสลับจริง ๆ เท่านั้น ไม่ใช่ทุกเฟรม — รายละเอียดเล็ก ๆ แต่สำคัญ เพราะระบบ AI ที่ตอบสนองทุกเฟรมไม่ว่าจะมีการเปลี่ยนแปลงหรือไม่จะกลายเป็นระบบที่ noisy และเปลืองงานคำนวณอย่างรวดเร็ว ทุกอย่างต่อจากนี้ — การเปลี่ยนสถานะที่ฉลาดขึ้น การหลบเข้าที่กำบัง การประสานงานเป็นกลุ่ม — ล้วนสร้างต่อยอดจากโครงเดียวกันนี้: "เช็คเงื่อนไข แล้วสลับ state"

Job Market

เล็กกว่า gameplay โดยรวม studio ขนาดเล็กและกลางจำนวนมากไม่มีตำแหน่ง "AI programmer" แยกต่างหากเลย — งานนี้ถูกรวมเข้าไปอยู่ในบทบาท gameplay programmer ทั่วไป ตำแหน่ง AI programmer แบบเฉพาะทางจะกระจุกอยู่ที่ studio ใหญ่ที่มีระบบ NPC ซับซ้อน (เกม open-world, เกมแนว tactics/strategy, ศัตรูจำนวนมากหลายแบบ) ที่ซึ่งโค้ด AI ใหญ่พอจนต้องมีคนดูแลเฉพาะทาง

8. Network / Backend Programmer

Network programmer ทำให้ multiplayer ทำงานได้: รักษาความ sync ของเกมผู้เล่นหลายคน ตัดสินว่าเครื่องคอมพิวเตอร์ไหน "ได้รับอนุญาต" ให้ถูกต้อง ตอนที่ผู้เล่นสองคนขัดแย้งกัน และสร้าง RPC call (remote procedure call — การเรียกฟังก์ชันที่โค้ดรันอยู่บนเครื่องคนละเครื่อง) ที่ทำให้ action ของผู้เล่นคนหนึ่งไปโผล่ถูกต้องบนหน้าจอของคนอื่นทุกคน network programmer หลายคนยังดูแล backend ด้วย — server ที่อยู่นอกตัวเกม client เอง: account, matchmaking, inventory, leaderboard, live event

A Day in the Life

tester รายงานว่าบน connection ที่แย่ ตัวละครของผู้เล่นคนอื่น "rubber-band" — กระตุกถอยหลังกะทันหัน เราสืบไปเจอว่า client-side prediction (client เดาตำแหน่งตัวละครไว้ล่วงหน้าก่อนที่ server จะยืนยัน) ขัดแย้งกับตำแหน่งที่ server ยืนยันแล้วมากเกินไป เราใช้เวลาทั้งวันปรับวิธี smooth การแก้ไขตำแหน่ง แล้วสลับไปเขียน backend endpoint ที่ให้ reward สำหรับ login รายวัน แล้วปิดวันด้วยการ load-test ด้วย script ที่จำลอง login ปลอม 500 ครั้งพร้อมกัน

What You Must Be Strong At

A Typical Junior Task

งาน networking งานแรกที่พบบ่อยเมื่อใช้ framework อย่าง Netcode for GameObjects ของ Unity: ทำให้ตำแหน่งของผู้เล่นคนหนึ่งอัปเดตถูกต้องบนหน้าจอของผู้เล่นคนอื่นทุกคน โดยให้ server เป็นฝ่าย authority (แหล่งความจริงเพียงหนึ่งเดียว เพื่อไม่ให้ผู้เล่น teleport ได้เองแค่แก้ตำแหน่ง local ของตัวเอง)

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;
    }
}
What each machine sees, one movement step: Client A (owner) -> sends RequestMoveServerRpc(newPos) to the server Server -> validates, sets networkPosition.Value = newPos Server -> auto-replicates networkPosition to every client Client A, Client B -> both read the SAME networkPosition.Value next frame Client A never moves itself directly -- it only asks. The server's copy of networkPosition is the one truth everyone else renders from.

Client A ไม่เคยตั้งตำแหน่งของตัวเองโดยตรงเลย — มันแค่ขอเท่านั้น server เป็นเครื่องเดียวที่เขียนค่าลงไปใน networkPosition จริง ๆ การไหลของข้อมูลแบบทิศทางเดียวนี้ (client ขอ, server ตัดสิน, ทุกคน render จากคำตอบของ server) คือแนวคิดหลักที่เรื่อง networking ที่เหลือทั้งหมดสร้างต่อยอดขึ้นไป รวมถึงปัญหาที่ยากกว่านี้อย่างการจัดการผู้เล่นที่ขอ move แบบที่เป็นไปไม่ได้

Job Market

ตำแหน่งงานเปิดรับน้อยกว่า gameplay แต่มั่นคง — เกม multiplayer หรือ live-service ทุกเกมต้องมี network programmer อย่างน้อยหนึ่งคน และประเภทเกมแบบนี้ก็ไม่ได้หดตัวลง เข้ายากจริง ๆ สำหรับ junior: บั๊กเรื่อง network มักไม่ deterministic (เกิดซ้ำแบบเดิมไม่ได้ทุกครั้ง) และต้องใช้ประสบการณ์ในการคิดวิเคราะห์อย่างมีประสิทธิภาพ track นี้จึงพึ่งพา mentorship อย่างหนักในช่วงแรก

9. UI Programmer

UI programmer สร้างเมนู, HUD (heads-up display — overlay บนหน้าจอที่โชว์เลือดม, กระสุน, minimap และอื่น ๆ คล้ายกัน), หน้าจอ inventory และเมนู settings แล้วเขียน code layer ที่เชื่อม visual เหล่านั้นเข้ากับข้อมูลและ event จริงของเกมที่อยู่เบื้องหลัง

A Day in the Life

เราเชื่อม shield bar ตัวใหม่ให้เติมและลดค่าถูกต้องควบคู่ไปกับ health bar ที่มีอยู่แล้ว QA รายงานว่า shield bar ทับกับ health bar บนจอ ultra-wide — บั๊กเรื่อง anchoring ที่โผล่มาเฉพาะบาง aspect ratio เท่านั้น เราแก้ layout แล้วใช้เวลาหนึ่งชั่วโมงตรวจสอบให้ controller เดิน grid ของ inventory ได้เหมือนกับที่ mouse ทำได้ เพราะทั้งสองต้องทำงานให้เหมือนกันเป๊ะ

What You Must Be Strong At

A Typical Junior Task

งาน UI งานแรกที่พบบ่อย: ทำให้ health bar ตอบสนองต่อ damage event แทนที่จะถูกเช็คและวาดใหม่ทุกเฟรม (ซึ่งเปลืองงานคำนวณและ 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") + "%)");
    }
}
Trace, maxHp = 100, three damage events fire: OnDamage(100, 100) -> fillAmount = 1.00 -> Health bar updated: 100/100 (100%) OnDamage(65, 100) -> fillAmount = 0.65 -> Health bar updated: 65/100 (65%) OnDamage(20, 100) -> fillAmount = 0.20 -> Health bar updated: 20/100 (20%)

bar ทำงานก็ต่อเมื่อ OnDamage เกิดขึ้นจริงเท่านั้น subscribe และ unsubscribe อย่างเป็นระเบียบใน OnEnable/OnDisable เพื่อไม่ให้มันแอบฟังต่อหลัง object หายไปแล้ว pattern แบบ event-driven นี้ — ตอบสนองต่อการเปลี่ยนแปลง ไม่ต้อง poll เช็คทุกเฟรม — คือแกนหลักของงาน UI programming แทบทั้งหมด

Job Market

ความต้องการมั่นคง — ทุกเกมต้องมี UI งานนี้เลยไม่มีวันหายไป แต่ที่ studio เล็ก ๆ มักถูกรวมเข้าไปในงาน gameplay ทั่วไปแทนที่จะเป็นตำแหน่งแยกต่างหาก ในที่ที่มีตำแหน่ง UI programmer แยกจริง ๆ มักแน่นน้อยกว่าตำแหน่ง "gameplay programmer" ทั่วไป เพียงเพราะมันฟังดูน่าตื่นเต้นน้อยกว่าและผู้สมัคร junior เล็งไปที่มันโดยตรงน้อยกว่า

10. Audio Programmer

Audio programmer เชื่อม sound effect และเพลงเข้ากับ event ของเกม สร้าง logic การ mix (ตัดสินว่าเสียงต่าง ๆ ควรดังแค่ไหนเทียบกัน และเมื่อไหร่) และ integrate audio middleware (toolkit จากบุคคลที่สาม — Wwise กับ FMOD เป็นสองตัวที่ใช้กันมากที่สุด — ที่ sound designer ใช้สร้าง interactive audio ได้โดยไม่ต้องพึ่ง programmer ทุกครั้งที่มีการเปลี่ยนแปลง)

A Day in the Life

sound designer รายงานว่าเสียงฝีเท้าดังสองครั้งบนพื้นคอนกรีตแต่ไม่เคยเกิดบนพื้นหญ้าเลย เราสืบไปเจอว่า animation event ทำงานสองครั้งต่อฝีเท้าใน animation clip หนึ่ง แต่ทำงานแค่ครั้งเดียวในอีก clip หนึ่ง — บั๊กเรื่อง animation ที่แต่งตัวมาเป็นบั๊กเรื่อง audio บ่าย: implement music ducking (ลดเสียงเพลงอัตโนมัติตอนตัวละครกำลังพูด) เพื่อให้บทพูดยังฟังชัดโดยไม่ต้องให้ sound designer มาคอยปรับ volume fader เองทุกบรรทัด

What You Must Be Strong At

A Typical Junior Task

งาน audio งานแรกที่พบบ่อย: implement music ducking ให้เพลงพื้นหลังเบาลงอัตโนมัติตอนบทพูดกำลังเล่นอยู่ แล้วกลับมาดังเหมือนเดิมตอนบทพูดจบ

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);
    }
}
Trace, dialogue starts playing at t=0, duckSpeed=2/sec, starting volume=1.0: t=0.0s dialogue.isPlaying=true target=0.25 music.volume=1.00 t=0.1s dialogue.isPlaying=true target=0.25 music.volume=0.80 t=0.2s dialogue.isPlaying=true target=0.25 music.volume=0.60 t=0.4s dialogue.isPlaying=true target=0.25 music.volume=0.25 (reached target) t=2.0s dialogue.isPlaying=false target=1.00 music.volume rising back toward 1.00

Mathf.MoveTowards เลื่อนค่า volume ไปหา target ที่ถูกต้อง ณ ตอนนั้นอย่างนุ่มนวล แทนที่จะกระโดดไปทันที — การกระโดดของ volume แบบทันทีทันใดสังเกตเห็นได้ชัดมากและฟังดูเหมือนความผิดพลาด ในขณะที่การเลื่อนแบบนุ่มนวลครึ่งวินาทีให้ความรู้สึกเหมือนการ mix ที่ตั้งใจทำ

Job Market

จำนวนตำแหน่งเฉพาะทางน้อยที่สุดในบรรดาทุก track ในบทนี้ studio จำนวนมากไม่มี audio programmer เฉพาะทางเลย — sound designer จัดการ scripting ง่าย ๆ เองบ้าง หรืองานถูกรวมเข้าไปใน gameplay/tools programming ตำแหน่งเฉพาะทางที่มีอยู่จริงกระจุกตัวอยู่ที่ studio ใหญ่และบริษัท middleware เอง (Audiokinetic, Firelight) และแข่งขันสูงเพราะมีที่นั่งน้อยมากพอดี

11. เปรียบเทียบแต่ละ Track

เมื่อวางเทียบกัน ทั้งเก้า track แยกออกจากกันตามแกนที่ค่อนข้างคงที่อยู่ไม่กี่แกน: งานประจำวันพึ่งพา math มากแค่ไหน ภาษาไหนครองสาย ประมาณว่ามีตำแหน่งงานเปิดรับเยอะแค่ไหน และปกติแล้วยากแค่ไหนที่จะเข้าไปในฐานะ junior ตัวเลขเหล่านี้ไม่ใช่ค่าที่แม่นยำเป๊ะ ๆ — มันแตกต่างกันไปตาม studio และภูมิภาค — แต่ลำดับเปรียบเทียบโดยรวมนี้ยังคงเป็นจริงสม่ำเสมอทั่วทั้งวงการ

TRACK MATH LANGUAGE JOB COUNT JUNIOR ENTRY ------------------------------------------------------------------------ Gameplay low-med C#/C++ highest hard (crowded) Graphics high C++/HLSL lowest hardest Engine/Systems med C++ low hard Tools low C#/C++/Python medium easiest Technical Art med Python/C#/HLSL medium hard (rare mix) AI low-med C#/C++ low-med medium Network/Backend med C++/C#/backend low-med hard UI low C#/C++ medium-high medium Audio low-med C#/C++ lowest hard (few seats)
Relative job opening volume (rough, illustrative only): Gameplay ################################ UI ####################### Tools #################### Technical Art ################## AI ############### Network ############# Engine ########### Audio ####### Graphics ######

The Honest Notes

ตัวเลขข้างบนซ่อนบางอย่างที่สำคัญกว่าจำนวนดิบ ๆ เอาไว้:

Tip Gameplay มีตำแหน่งงานเปิดรับมากที่สุดในบรรดาทุก track และก็มีการแข่งขันสูงที่สุดด้วยเช่นกัน แทบทุก portfolio ของ junior เล็งไปที่มัน จำนวนประกาศงานที่เยอะจึงไม่ได้แปลว่ามันเป็น track ที่เข้าง่ายที่สุด
Tip Tools programming เป็นหนึ่งในทางที่เข้าง่ายที่สุด และถูกผู้สมัครมองข้ามคุณค่าไป มันมี skill set ทับซ้อนกับ gameplay เป็นส่วนใหญ่ ฟังดูน่าตื่นเต้นน้อยกว่าในชื่อตำแหน่ง และสร้าง portfolio piece ของ junior ที่ demo ได้ชัดเจนที่สุดบางชิ้น — tool ที่ทำเสร็จแล้วทำงานได้จริงเห็นชัด ๆ แสดงให้ดูตอนสัมภาษณ์ได้ง่าย
Common mistake คิดว่า graphics เป็นแค่ "สายที่ทำให้สวย" Graphics คือ track ที่ต้องการเทคนิคสูงที่สุดในบทนี้ และยากที่สุดสำหรับ junior ที่จะเข้าไปโดยไม่มีหลักฐานฝีมือมาก่อน — แต่มันก็เป็น track ที่ย้ายไปใช้นอกวงการเกมได้มากที่สุดด้วยเช่นกัน เพราะคณิตศาสตร์ rendering แบบเดียวกันนี้ใช้ในซอฟต์แวร์ภาพยนตร์, simulation และ visualization
Tip Technical art อยู่ตรงกลางระหว่างสายอาชีพ art กับ programming พอดี และ studio มักดิ้นรนหาคนที่เก่งจริงทั้งสองด้านอยู่เรื่อย ๆ ถ้าคอมโบนี้ตรงกับเราจริง ๆ track นี้มีความต้องการสูงกว่าที่ตัวเลขจำนวนตำแหน่งงานเพียงอย่างเดียวจะบอกได้

12. วิธีทดสอบว่า Track ไหนเหมาะกับเรา

อ่านคำอธิบายอย่างเดียวพาเราไปได้แค่ระดับหนึ่ง — แทบทุก track ฟังดูน่าสนใจในหนึ่งย่อหน้าทั้งนั้น การทดสอบจริง ๆ คือการสร้างของเล็ก ๆ ในแต่ละ track แล้วสังเกตสัญญาณที่เฉพาะเจาะจงมาก: ไม่ใช่ "ทำเสร็จไหม" แต่คือ ยังคิดถึงมันอยู่หรือเปล่าหลังจากปิดโน้ตบุ๊กไปแล้ว สัญญาณตัวที่สองนี้ซื่อสัตย์กว่าความสนุกที่รู้สึกระหว่างสองชั่วโมงที่ใช้ทำมันมาก เพราะมีงานหลายอย่างที่สนุกตอนทำแต่ลืมได้ทันที และมีบางอย่างที่น่ารำคาญนิด ๆ ตอนทำแต่ดันดึงความสนใจกลับมาตอนเย็นวันนั้นอยู่ดี

Nine Small Projects, One Per Track

แต่ละอันนี้เล็กพอจะลองทำในหนึ่งสุดสัปดาห์ได้ โดยใช้แค่สิ่งที่บทก่อน ๆ สอนไปแล้ว บวกกับ engine API เฉพาะที่ต้องไปค้นหาเพิ่มระหว่างทำ:

Build one -> notice the signal -> repeat for the next track [ build small thing ] | v [ close the laptop ] | v later that day: am I still turning this over in my head, wanting to add "just one more" feature to it? | yes -+----------------------------> strong signal, worth | a longer follow-up project v no, forgot about it within the hour -----> weak signal for this track, move to the next one

Self-Assessment Checklist

หลังลองทำสักสองสามอย่างในเก้าอันนี้ คำถามพวกนี้มักช่วยจัดกลุ่มคนได้เร็ว ความซื่อสัตย์สำคัญกว่าการตอบ "ใช่" กับข้อที่ฟังดูน่าประทับใจ:

คนส่วนใหญ่ติ๊กถูกกระจายอยู่สองสาม track ไม่ใช่แค่ track เดียว — การซ้อนทับกันแบบนี้เป็นเรื่องปกติและเป็นข้อมูลที่มีประโยชน์ ไม่ใช่ความล้มเหลวในการหาคำตอบเดียวที่ "ถูกต้อง"

13. Studio แต่ละแบบต่างกันยังไง และงานแรกไม่ใช่ตลอดชีวิต

ชื่อ track เดียวกันอาจหมายถึงงานที่ต่างกันชัดเจนขึ้นอยู่กับว่าเราไปอยู่ studio แบบไหน สามรูปแบบกว้าง ๆ นี้ครอบคลุมวงการส่วนใหญ่:

MOBILE LIVE-SERVICE CONSOLE / PC AAA INDIE (e.g. gacha/live games, (e.g. big single-player (small teams, frequent content patches) or competitive titles) self-published) --------------------------------------------------------------------------------- Update cadence: Update cadence: Update cadence: every 2-6 weeks every 1-3+ years whenever, if ever (post-launch patches) Team shape: Team shape: Team shape: large, sharply split into large, sharply split into tiny, roles blur tracks; tools/UI/network tracks; graphics/engine together; one person teams especially big teams especially big often covers 3-4 tracks Engine: Engine: Engine: usually an existing engine often in-house or a usually an existing (Unity/Unreal), less engine heavily modified engine engine, minimal custom work, more content-pipeline work engine work Where the pressure is: Where the pressure is: Where the pressure is: shipping content fast and visual/technical fidelity doing everything with safely, live-ops tooling over a long production very few people

studio แบบ mobile live-service (แนวเดียวกับที่เกมสไตล์ HoYoverse ถูกสร้างขึ้นมา) พึ่งพา tools กับ network/backend programmer อย่างหนัก เพราะการปล่อย event ใหม่ทุกไม่กี่สัปดาห์จะทำได้ก็ต่อเมื่อคนที่ไม่ใช่ programmer ประกอบมันเองได้ผ่าน tool ที่ดี และเพราะระบบเศรษฐกิจ, account, event ของเกม live ทั้งหมดวิ่งผ่าน backend system ภาระงาน UI programming ก็หนักเช่นกัน — banner, หน้าจอ event, เมนูหมุนเวียนตลอดเวลา

studio แบบ console/PC AAA พึ่งพา graphics กับ engine programmer อย่างหนัก เพราะความสวยงามทาง visual ตลอด production ที่ยาวหลายปีมักเป็นตัวสินค้าจริง ๆ ที่ขายอยู่ และทีมมักใหญ่พอที่จะแยก track ออกจากกันอย่างชัดเจน — การย้าย track ในบริษัทเดียวกันอาจใช้เวลานานกว่าที่ studio เล็กกว่า

studio แบบ indie แทบไม่มี track แยกกันเลย ทีมที่มี programmer สามสี่คนหมายความว่าทุกคนทำทั้ง gameplay นิดหน่อย tools นิดหน่อย บางทีก็ graphics นิดหน่อย ด้วยความจำเป็นไม่ใช่ทางเลือก การ specialize ทำได้น้อยกว่า แต่ข้อดีคือความเป็นเจ้าของงานที่กว้างขึ้นและ feedback ที่เร็วและตรงมากขึ้นจากงานของตัวเอง

Tip ไม่มีอันไหนเป็นโทษตลอดชีวิต programmer ย้าย track กันอยู่ตลอดเวลา — gameplay programmer ที่หยิบงาน shader มาทำจนเก่งพอจะขยับไปทาง technical art, tools programmer ที่คอย optimize tool ของตัวเองจนขยับไปทาง engine work, UI programmer ที่อยากรู้เรื่อง networking แล้วขยับไปทางนั้นแทน วิธีทั่วไปในการย้ายคือนิสัยแบบโปรเจกต์สุดสัปดาห์จากหัวข้อ 12 นั่นแหละ: สร้างของชิ้นหนึ่งที่แน่นในสายใหม่ แล้วใช้มันขอย้ายไปเลย แทนที่จะรอให้ใครมาชวน

พื้นฐานกว้าง ๆ จากบทก่อน ๆ นี่แหละที่ทำให้การย้ายแบบนั้นเป็นไปได้ในภายหลัง skill เรื่อง C++, C# และ data structures ที่อยู่เบื้องหลังทุก track ในบทนี้คือ skill ชุดเดียวกัน — แค่เล็งไปคนละทิศทางขึ้นอยู่กับว่าปีนี้เราอยู่ track ไหน

Glossary

Exercise 1 นี่คือตัวอย่างประกาศงานสไตล์จริง:
"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."
ประกาศงานนี้เป็นของ track ไหนในบทนี้? เขียนสองประโยคข้อกำหนดใหม่ด้วยภาษาง่าย ๆ ที่ผู้เริ่มต้นเข้าใจได้
Show answer

นี่คือประกาศงานของ graphics/rendering programmer มีเบาะแสสามอย่างที่ชี้ตรงไปที่มัน: "3D math" (linear algebra), "shader authoring" (เขียนโค้ด HLSL/GLSL) และ "GPU profiling tools" (เครื่องมือสไตล์ RenderDoc/Nsight) ซึ่งทั้งหมดถูกระบุตรง ๆ ในรายการ "What You Must Be Strong At" ของหัวข้อ 3

เขียนใหม่ด้วยภาษาง่าย ๆ: "เราต้องคุ้นเคยกับ vector และ matrix และเขียนโปรแกรมเล็ก ๆ (shader) ที่รันบนการ์ดจอได้ เรารู้วิธีใช้เครื่องมือที่โชว์ว่าแต่ละส่วนของเฟรมใช้เวลาบน GPU นานแค่ไหน เพื่อให้เกมรันเร็วขึ้นได้โดยไม่ทำให้ภาพแย่ลง"

Exercise 2 ผู้สมัคร junior คนหนึ่งอธิบายตัวเองแบบนี้: "ฉันชอบปัญหาที่ชัดเจนและ deterministic มีคำตอบที่ถูกต้องแค่คำตอบเดียว feedback ที่คลุมเครืออย่าง 'ทำให้มันรู้สึกดีขึ้น' ทำให้ฉันหงุดหงิดมากกว่าจะกระตุ้นให้ทำงาน ฉันชอบบทเรื่อง memory กับ pointer มากกว่าบทที่มี UI เกี่ยวข้อง ฉันไม่มีสายตาทางศิลปะที่แข็งแรง" อ้างอิงจากตารางในหัวข้อ 11 และคำอธิบายในบทนี้ บอกชื่อสอง track ที่เหมาะกับโปรไฟล์นี้ที่สุด และ track ที่เหมาะน้อยที่สุดหนึ่ง track พร้อมเหตุผลอย่างละหนึ่งประโยค
Show answer

เหมาะที่สุด: Engine/Systems และ Tools Engine/Systems สร้างขึ้นบนเนื้อหาเรื่อง memory กับ pointer โดยตรง และให้ผลตอบแทนกับความถูกต้องแบบ deterministic ที่วัดผลได้ แทนที่จะเป็นการปรับจูน "ความรู้สึก" แบบอัตวิสัย — ตรงกับสิ่งที่ผู้สมัครบอกว่าชอบและต้องการพอดี Tools ก็ให้ผลตอบแทนกับความถูกต้องแบบ "ทำงานได้หรือไม่ได้" ที่ชัดเจนเช่นกัน มี feedback เรื่องความรู้สึกของผู้เล่นที่คลุมเครือแบบที่ gameplay programmer ต้องเจอทุกวันน้อยกว่ามาก และไม่ต้องการสายตาทางศิลปะที่ผ่านการฝึกมา

เหมาะน้อยที่สุด: Technical Artist หัวข้อ 6 ระบุ "สายตาที่ผ่านการฝึกฝนมาจริง ๆ สำหรับคุณภาพงาน visual" เป็นข้อกำหนดที่ไม่ใช่ตัวเลือกเสริม ซึ่งผู้สมัครบอกตรง ๆ ว่าตัวเองไม่มี และงานส่วนใหญ่ถูกตัดสินด้วย feedback แบบอัตวิสัย "มันดูถูกต้องไหม" ซึ่งใกล้เคียงกับ feedback ที่คลุมเครือแบบที่ผู้สมัครบอกว่าทำให้หงุดหงิด

Exercise 3 เพื่อนร่วมทีมส่ง snippet นี้มาให้แล้วถามว่า track ไหนในทีมควรเป็นเจ้าของโค้ดนี้:
// 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);
    }
}
บอกชื่อ track แล้วให้เหตุผลสองข้อที่ดึงมาจากรายการ "What You Must Be Strong At" ของ track นั้นในบทนี้
Show answer

โค้ดนี้เป็นของ network/backend programmer เหตุผลสองข้อ ทั้งคู่มาจากหัวข้อ 8 โดยตรง:

1. มันถูกเขียนเป็น [ServerRpc] — โค้ดที่รันบน server เท่านั้นเสมอ ตรงกับ pattern "server เป็น authority" จากงาน junior task ในหัวข้อ 8 ที่ server เป็นฝ่ายตัดสินผลลัพธ์จริง ส่วน client แค่ขอเท่านั้น

2. การตัดสินว่ากระสุนนัดหนึ่งโดนเป้าจริงไหม แล้วค่อยบอกทุก client ให้ apply damage ทีหลัง ต้องใช้ "การคิดอย่างระมัดระวังเรื่อง state กับ timing" ตรงตามข้อในรายการ "What You Must Be Strong At" ของหัวข้อ 8 เป๊ะ — server ต้องเป็นแหล่งความจริงเพียงหนึ่งเดียว ไม่งั้นผู้เล่นสองคนอาจเห็นผลลัพธ์ของกระสุนนัดเดียวกันไม่ตรงกันได้

(gameplay programmer อาจเป็นคนเขียนตัวเลข damage ดิบ ๆ หรือ logic ของ raycast ในบริบทเกมผู้เล่นคนเดียวได้ แต่ทันทีที่มันถูกห่อด้วยคู่ ServerRpc/ClientRpc ความเป็นเจ้าของก็ตกอยู่กับใครก็ตามที่รับผิดชอบรักษาให้เกมของผู้เล่นทุกคนตรงกัน)

← กลับไปหน้ารวมบท