มาถึงตอนนี้คุณสร้างของจริงมาแล้วหลายอย่างระหว่างเรียนหลักสูตรนี้ ทั้ง data structure, ระบบ gameplay เล็ก ๆ, บางทีก็ shader หรือ tool สักตัว แต่งานพวกนั้นจะไม่มีประโยชน์อะไรเลยตอนสมัครงาน ถ้าไม่มีใครนอกหัวของคุณเห็นมัน เข้าใจว่าคุณสร้างอะไร แล้วตัดสินได้ว่าคุณจะทำแบบนี้ซ้ำได้อีกไหมถ้าอยู่ในทีมของเขา นั่นคืองานทั้งหมดของ portfolio (ชุดผลงานสาธารณะที่คัดสรรมาอย่างดี สร้างมาให้คนแปลกหน้าตัดสินภายในเวลาจำกัด) หัวข้อนี้จะพูดถึงว่าทีมที่จ้างงานเช็คอะไรจริง ๆ ตอนเปิด portfolio ของคุณ ทำไม quality ถึงชนะ quantity แบบขาดลอย strong piece ของแต่ละสาย programming และ art หน้าตาเป็นยังไง ทำไม presentation ถึงกินคะแนนไปครึ่งหนึ่ง วิธีเขียนอธิบายโปรเจกต์ให้คนแปลกหน้าเข้าใจได้ในเวลาไม่ถึงนาที วิธีทำให้โค้ดกับ README ของคุณอ่านง่ายสำหรับคนที่ไม่เคยเห็น codebase คุณมาก่อน ควรเอาผลงานไปโพสต์ที่ไหน และแผนสร้าง portfolio แรกที่ทำได้จริงเมื่อคุณเรียนหลักสูตรนี้จบ
เรซูเม่ที่เขียนว่า "เคยใช้ Unity, C#, git" ไม่ได้บอกอะไรที่ทีมที่จ้างงานตรวจสอบได้เลย ใครก็พิมพ์ประโยคนี้ได้ไม่ว่าจริงหรือไม่จริง แต่ portfolio piece ต่างออกไป มันคือลิงก์ที่เขาเปิดดูได้เดี๋ยวนี้ แล้วเช็คด้วยตัวเอง นี่คือเหตุผลทั้งหมดที่ portfolio สำคัญกว่าลิสต์คำกล่าวอ้าง
ตอนที่ผู้พิจารณาเปิด portfolio ของคุณ จริง ๆ แล้วเขาเช็คอยู่สองอย่าง และแทบทุกอย่างในหัวข้อนี้มีไว้ช่วยให้คุณพิสูจน์ทั้งสองอย่างนั้น
ยังมีสิ่งที่สามที่ถูกเช็คไปพร้อมกันแบบเงียบ ๆ อีกอย่าง คือคุณยอมเสียเวลาของตัวเองมากแค่ไหนเพื่อทำให้คนอื่นตัดสินผลงานคุณได้ง่ายขึ้น ผู้พิจารณาที่มีใบสมัครกองเป็นตั้งไม่ได้เป็นหนี้คุณที่ต้องมาขุดหา repository รกๆ เพื่อหาส่วนที่น่าสนใจ หัวข้อ 8 กับหัวข้อ 10 จะกลับมาพูดเรื่องนี้ตรง ๆ แต่เริ่มไว้ตรงนี้ก่อนด้วยข้อเท็จจริงที่ควรจำไว้: ผู้พิจารณาทั่วไปใช้เวลาประมาณสามสิบวินาทีถึงสองนาทีในการตัดสินใจว่าจะดู portfolio อันไหนต่อให้ลึกขึ้น
ไม่มีอะไรใน timeline นี้ที่ไม่แฟร์หรือขี้เกียจในฝั่งผู้พิจารณาเลย สตูดิโอที่เปิดรับหนึ่งตำแหน่งอาจได้ใบสมัครเป็นร้อย ๆ การสแกนสามสิบวินาทีไม่ใช่การปฏิเสธความพยายามของคุณ — มันคือ filter และหน้าที่ของคุณคือทำให้สามสิบวินาทีแรกของ portfolio รอดจาก filter นี้ให้ได้
เปิด GitHub profile ของมือใหม่เกือบทุกคนดู คุณจะเห็นแพทเทิร์นที่คุ้นเคย: repository สิบสองถึงยี่สิบตัว ส่วนใหญ่ชื่อประมาณ Unity-Tutorial-Follow-Along, MyGame, หรือ MyGame2-final-FINAL commit history มักบอกเรื่องจริงอยู่แล้ว: commit รัว ๆ วันแรกกับวันที่สอง แล้วก็เงียบไปเลย ไม่มี README หรือมีแต่ยังเขียนว่า "TODO: write description" ไม่มีวิดีโอ ไม่มี screenshot บางทีถึงขั้นไม่รู้ว่าต้องรัน scene ไหนถึงจะเปิดโปรเจกต์ขึ้นมาได้
นี่ไม่ใช่นิสัยเสียอะไร มันเกิดขึ้นเพราะการตาม tutorial รู้สึกเหมือนได้ผลผลิตจริง ๆ — คุณพิมพ์โค้ดอยู่ วิดีโอมีความคืบหน้าให้เห็นตลอด แล้วจบแต่ละ session คุณก็มีโค้ดมากกว่าตอนเริ่ม แต่การตาม tutorial เป็นทักษะคนละอย่างกับการสร้างของจากไฟล์เปล่าแล้วตัดสินใจเองตอนมันพังในแบบที่ tutorial ไม่เคยพูดถึง tutorial ต่อให้ทำจนจบตามที่สอนเป๊ะ ๆ ส่วนใหญ่ก็แค่พิสูจน์ว่าคุณทำตามคำสั่งได้ ไม่ได้พิสูจน์ว่าคุณ debug การตัดสินใจออกแบบของตัวเองได้ และการ debug การตัดสินใจของตัวเองนี่แหละคืองานจริง ทุกวัน
ทั้งหมดนี้ไม่ได้แปลว่า tutorial เสียเวลาเปล่า มันเป็นวิธีที่เร็วที่สุดวิธีหนึ่งในการเรียนเทคนิคใหม่ และหลักสูตรนี้เองก็ใช้ตัวอย่างเล็ก ๆ แบบมีไกด์มาตลอดด้วยเหตุผลเดียวกัน ข้อผิดพลาดคือการเอา tutorial ที่ทำเสร็จมาใส่เป็น portfolio piece มันจะกลายเป็น portfolio piece ได้ก็ต่อเมื่อคุณเอาไปต่อยอดไกลกว่าที่ tutorial สอนมาก ๆ: เปลี่ยนกลไก, เพิ่มระบบที่ tutorial ไม่เคยพูดถึง, สร้างส่วนสำคัญใหม่ในแบบของตัวเอง แล้วซื่อสัตย์ใน writeup ว่าส่วนไหนมีไกด์ ส่วนไหนเป็นของคุณเอง
นี่คือกฎที่สำคัญที่สุดข้อเดียวในหัวข้อนี้ และควรอธิบายว่าทำไมมันถึงจริง ไม่ใช่แค่พูดลอย ๆ เวลาของผู้พิจารณาคือทรัพยากรที่จำกัดในกระบวนการทั้งหมดนี้ ไม่ใช่จำนวนโปรเจกต์ของคุณ โปรเจกต์ที่ค้างสิบตัวทำให้ผู้พิจารณาต้องคลิกสิบครั้ง และหลังจากตัวที่สองที่รันไม่ได้หรือไม่มีอะไรให้ดูเกินวันที่สอง เขาก็เลิกคลิก — ไม่ใช่เพราะโหดร้าย แต่เพราะคลิกพลาดเก้าครั้งติดกันเป็นเหตุผลที่สมเหตุสมผลมากพอที่จะเลิกเชื่อลิงก์ที่สิบ ในขณะที่โปรเจกต์เสร็จแล้วขัดเกลาแล้วสามตัวใช้แค่สามคลิก และทุกคลิกให้รางวัลด้วยของจริงให้ดู เล่น หรืออ่าน แพทเทิร์นที่สองสร้างความเชื่อใจทุกคลิก แพทเทิร์นแรกทำลายมัน
มีเหตุผลที่สองที่ quality ชนะ ซึ่งไม่เกี่ยวกับความอดทนของผู้พิจารณาเลย การทำโปรเจกต์ให้เสร็จคือสิ่งที่ทำให้ยี่สิบเปอร์เซ็นต์ที่ยากที่สุดของงานโผล่ขึ้นมา: บัคน่าเบื่อตาม edge case, ช่วงเวลาที่กลไกทำงานได้ในทางเทคนิคแต่รู้สึกผิด แล้วคุณต้องหาให้เจอว่าทำไม, การขัดเกลารอบสุดท้ายที่เปลี่ยน "มันรันได้" ให้กลายเป็น "มันไม่น่าอาย" tutorial ถูกออกแบบมาให้ข้ามยี่สิบเปอร์เซ็นต์นั้นเกือบทั้งหมด เพราะหน้าที่ของ tutorial คือพาคุณไปถึงผลลัพธ์ที่ใช้งานได้ตามตารางเวลา ไม่ใช่ทำให้คุณนั่งอยู่กับระบบครึ่ง ๆ กลาง ๆ ที่ดูแย่จนกว่าจะเข้าใจว่าทำไมมันถึงแย่ ยี่สิบเปอร์เซ็นต์นั้นแหละคือส่วนที่แยกคนทำเป็นงานอดิเรกออกจากคนที่พร้อมอยู่ในทีมจริง ๆ และมันจะโผล่ออกมาก็ต่อเมื่อคุณดันโปรเจกต์ไปจนเสร็จเท่านั้น
มีกฎง่าย ๆ ที่ตามมาจากตรงนี้โดยตรง: ถ้าโปรเจกต์ยังไม่เสร็จพอที่คนแปลกหน้าจะเปิดดูแล้วเข้าใจว่ามันทำอะไรได้ภายในประมาณสองนาที โดยไม่มีคุณยืนอธิบายอยู่ข้าง ๆ มันยังไม่ใช่ portfolio piece มันเป็นแค่ work in progress และหัวข้อ 12 จะพูดถึงมาตรฐานที่ต่ำกว่าสำหรับของแบบนั้นแทน
ชิ้นงาน gameplay programming ถูกตัดสินจากสองอย่างพร้อมกัน: กลไก feel ดีไหม และโค้ดที่อยู่เบื้องหลังมองเห็นได้และมีเหตุผลรองรับไหม strong piece แทบไม่ใช่เกมทั้งเกมเลย มันคือกลไกเล็ก ๆ ที่แน่นและทำออกมาดีมากชิ้นเดียว — jump, dash, dodge roll, grapple hook, gravity-flip การพยายามทำเกมทั้งเกมที่มีห้าระบบ แต่ละระบบทำได้แค่หกสิบเปอร์เซ็นต์ ได้ portfolio piece ที่อ่อนแอกว่ากลไกเดียวที่ทำได้เก้าสิบห้าเปอร์เซ็นต์ ด้วยเหตุผลเดียวกับที่อาหารสามคอร์สที่ทุกจานสุกไม่ทั่วถึงแย่กว่าจานเดียวที่ทำสุกสมบูรณ์แบบ
"feel ดี" ไม่ใช่คำโฆษณาคลุมเครือในที่นี้ — มันคือชุดเทคนิคที่เรียนรู้ได้แบบเจาะจง และการเรียกชื่อมันใน writeup ของคุณก็เป็นส่วนหนึ่งของการพิสูจน์ว่าคุณเข้าใจสิ่งที่สร้างขึ้นมา สองอย่างที่พบบ่อยที่สุดสำหรับ movement:
writeup ที่ดีสำหรับ gameplay piece จะเรียกตัวเลขที่ปรับจริง ไม่ใช่แค่บอกว่ามีการปรับเกิดขึ้น: "coyote time เริ่มที่ 0.05 วินาทีแล้วรู้สึก unresponsive ตอน playtest ฉันปรับเป็น 0.12 วินาทีแล้วรู้สึกแฟร์โดยไม่ปล่อยให้ผู้เล่นโกง jump ช้าเห็นได้ชัด" ประโยคเดียวนั้นพิสูจน์ว่ามีการ iterate เกิดขึ้นจริง ซึ่งมีค่ากับผู้พิจารณามากกว่าคำกล่าวอ้างว่ากลไก "feel ดีมาก"
โค้ดสำคัญพอ ๆ กับ feel และต้องหาเจอง่าย ลิงก์ตรงไปที่ไฟล์เดียวที่สำคัญ — สำหรับ jump mechanic อาจจะเป็น PlayerController.cs หรือ JumpSystem.cs ไฟล์เดียว — แทนที่จะให้ผู้พิจารณาต้องขุดทั้ง repository ระหว่างการสแกนสามสิบวินาที หัวข้อ 10 จะพูดถึงวิธีทำให้ไฟล์เดียวนั้นอ่านง่ายเมื่อเขาเปิดมันขึ้นมา
ชิ้นงาน graphics ถูกตัดสินจากว่าคุณอธิบายเทคนิค rendering ด้วยคำพูดของตัวเองได้ไหม ไม่ใช่แค่ภาพสุดท้ายดูเท่ไหม ภาพ screenshot สวย ๆ อย่างเดียวแทบไม่พิสูจน์อะไรเลย ใครก็โหลด shader จาก asset store มาแล้วถ่าย screenshot ได้ หลักฐานความเข้าใจมาจากสามอย่างรวมกัน: ภาพ before ที่ชัดเจน ภาพ after ที่ชัดเจน และคำอธิบายเทคนิคด้วยคำพูดของตัวเองที่แสดงว่าคุณรู้ว่าทำไมภาพ after ถึงออกมาแบบนั้น
สังเกตว่าคำอธิบายนั้นไม่ได้แค่พูดว่า "ฉันเพิ่ม rim lighting" มันอธิบายไอเดียเชิงเรขาคณิตจริง ๆ (มุมระหว่าง surface normal กับ view direction) และผลลัพธ์ภาพจริง ๆ (สว่างขึ้นที่มุม grazing จางลงเมื่อมองตรง) ระดับรายละเอียดแบบนี้คือสิ่งที่แยก "ฉันเข้าใจเทคนิคนี้" ออกจาก "ฉันตาม tutorial แล้วมันได้ผล"
writeup graphics ที่ดียังต้องเรียกชื่อ tradeoff ด้วย ไม่ใช่แค่ผลชนะ ทำไมถึงเลือกวิธีนี้แทนอีกวิธี — เช่น ทำไมเลือกเทคนิคแบบ screen-space แทนแบบ per-vertex หรือทำไมเลือก approximation ที่ถูกกว่าแทนที่จะแม่นยำทางฟิสิกส์ แล้ว approximation นั้นเสียอะไรไปบ้างใน edge case การเรียกชื่อ tradeoff แบบตั้งใจคือสัญญาณความเข้าใจจริงที่ผู้พิจารณาสาย graphics มองเห็นชัดที่สุดใน writeup สั้น ๆ
งานสาย tools มีปัญหาเรื่อง presentation ที่สายอื่นไม่มี: งานมักมองไม่เห็นเลยเว้นแต่คุณจะตั้งใจสาธิตมัน วิดีโอ gameplay หรือภาพ shader before/after พูดแทนตัวเองได้อยู่แล้ว แต่ level-editor extension, batch-renaming script, หรือ build pipeline ที่ดีขึ้น ไม่มีอะไรน่าสนใจทางภาพเลยด้วยตัวมันเอง — คุณค่าทั้งหมดของมันคือ workflow ที่มันแทนที่ และ workflow คือสิ่งที่ต้องแสดงให้เห็นว่ามันเกิดขึ้นจริง screenshot จับภาพมันไม่ได้
หลักฐานที่แข็งแรงที่สุดสำหรับชิ้นงาน tools คือวิดีโอบันทึกหน้าจอสั้น ๆ ที่โชว์ before กับ after ติดกัน: "นี่คืองานเดียวกันที่ทำแบบ manual วิธีเก่า ใช้เวลาเท่านี้ — นี่คืองานเดียวกันทำด้วย tool ของฉัน ใช้เวลาเท่านี้" ตัวอย่างที่จับต้องได้: tool ที่ rename และเรียงเลขไฟล์ level ในโฟลเดอร์ใหม่ให้เรียงถูกต้องใน engine แบบ manual นักออกแบบอาจใช้เวลาสิบห้านาทีลากแล้ว rename ไฟล์สี่สิบไฟล์ทีละไฟล์ เสี่ยงพลาดได้ง่าย แต่ด้วย tool ผลลัพธ์เดียวกันใช้แค่กดปุ่มเดียวกับเวลาไม่กี่วินาที ความต่างนั้น ถ้าดูจริง ๆ ไม่ใช่แค่อ่านคำอธิบาย คือสิ่งที่ทำให้ผู้พิจารณาเข้าใจคุณค่าได้ทันที
Technical artist อยู่ระหว่าง art กับ code และชิ้นงานที่ดีต้องโชว์ทั้งสองฝั่งของสะพานนี้ ศิลปินส่วนใหญ่โชว์แค่ผลลัพธ์สุดท้ายที่สวยงาม แต่ portfolio piece ของ technical artist ต้องโชว์ วิธีการ ด้วย เพราะจุดประสงค์ทั้งหมดของ role นี้คือการสร้างเทคนิคที่ศิลปินคนอื่นในทีมเอาไปใช้ซ้ำและปรับแต่งได้ ไม่ใช่ภาพสุดท้ายที่ทำครั้งเดียวแล้วจบ
มีสองฟอร์แมตที่ทำสิ่งนี้ได้ดี breakdown คือชุดภาพทีละขั้นที่โชว์ effect ถูกสร้างขึ้นทีละเลเยอร์ — เช่น effect ไฟที่โชว์เป็นรูปทรง particle เปล่า ๆ ก่อน แล้วเพิ่ม color gradient แล้วเพิ่ม distortion แล้วเพิ่ม glow pass สุดท้าย เพื่อให้คนดูเห็นชัดว่าแต่ละเลเยอร์ช่วยอะไรบ้าง node graph คือกราฟ visual scripting จริง ๆ (shader graph, material graph, กราฟสไตล์ Blueprint) โชว์พร้อม annotation ของคุณเองที่อธิบายว่าแต่ละ node ทำอะไรและทำไมถึงต่อกันแบบนั้น
กราฟที่มี annotation แบบนี้ทำสิ่งที่ภาพ render สุดท้ายทำไม่ได้: มันให้ผู้พิจารณาตรวจสอบได้ทีละ node ว่าคุณเข้าใจจริงว่าแต่ละส่วนของกราฟทำอะไร แทนที่จะต้องเชื่อว่าผลลัพธ์สวย ๆ นั้นไม่ได้ประกอบขึ้นมาจากการลองผิดลองถูกจนบังเอิญออกมาสวย ทั้งสองแบบเป็นวิธีสร้างของที่ใช้ได้ แต่มีแค่แบบเดียวที่พิสูจน์ว่าคุณสร้างมันขึ้นมาใหม่ได้ ปรับแต่งได้ หรือสอนคนอื่นได้ — ซึ่งเป็นสิ่งที่ role technical art ต้องการให้คุณทำจริง ๆ
portfolio piece สองชิ้นที่คุณภาพเบื้องหลังเท่ากันเป๊ะ ๆ อาจได้ปฏิกิริยาที่ต่างกันโดยสิ้นเชิงขึ้นอยู่กับว่านำเสนอยังไง และควรพูดตรง ๆ ว่าทำไม: ผู้พิจารณาที่สแกนใบสมัครหลายสิบใบในบ่ายเดียวแยกไม่ออกระหว่าง "ไม่มีโปรเจกต์" กับ "โปรเจกต์ดีแต่อธิบายแย่" ข้อความยาว ๆ ที่ไม่มีภาพเลยก็อ่านเหมือนข้อความยาว ๆ ไม่ว่ากลไกข้างในจะดีแค่ไหน presentation ไม่ใช่ของประดับบนงานจริง — สำหรับสามสิบวินาทีแรกที่ผู้พิจารณาให้ความสนใจ presentation คือ งานจริง เพราะมันคือส่วนเดียวที่เขาจะได้เห็นจริง ๆ
สูตรตามลำดับ จากบนลงล่างของหน้า:
วิดีโอกับ GIF มาก่อนข้อความด้วยเหตุผลที่จับต้องได้ ไม่ใช่แค่ธรรมเนียม: screenshot นิ่ง ๆ ของกลไกที่เน้น movement หรือ feel แทบไม่มีประโยชน์เลย เพราะคุณเห็น feel ในภาพนิ่งไม่ได้ shader ต้องถูกดูตอนเคลื่อนไหวภายใต้แสงที่เปลี่ยนไป tool ต้องถูกดูอยู่ใน workflow จริง ๆ ที่มันช่วยให้เร็วขึ้น วิดีโอเป็นสื่อเดียวในลิสต์นี้ที่พิสูจน์คำกล่าวอ้างแบบ "มัน feel ดี" ได้จริง ไม่ใช่แค่กล่าวอ้างลอย ๆ ซึ่งตรงกับหลักฐานแบบที่หัวข้อ 1 บอกว่าผู้พิจารณากำลังมองหาพอดี
project write-up มีหน้าที่เดียว: ให้คนแปลกหน้าที่ไม่เคยเห็นโค้ดคุณมาก่อนเข้าใจ ภายในเวลาไม่ถึงนาที ว่าคุณสร้างอะไร ทำไมถึงสร้างแบบนั้น และเรียนรู้อะไรระหว่างทำ โครงสร้างสี่ส่วนด้านล่างนี้ทำหน้าที่นั้นได้อย่างน่าเชื่อถือ เรียงตามลำดับที่คำถามของผู้พิจารณาที่อยากรู้จะเกิดขึ้นตามธรรมชาติ
นี่คือ write-up ที่มือใหม่เกือบทุกคนเคยเขียนสักครั้ง มันไม่ได้ผิดขนาดนั้น — แค่มันไม่บอกอะไรที่ผู้พิจารณาเอาไปตัดสินอะไรได้เลย
This is a cool platformer game I made using Unity. It has jumping
and enemies. I used C# for the scripts. Check it out, link below!
อ่านแบบผู้พิจารณา: มันไม่ได้บอกว่า jump แก้ปัญหาอะไร อะไรที่ทำให้มันยาก หรือคนเขียนอยากเปลี่ยนอะไร มันอาจจะเป็นโปรเจกต์สุดสัปดาห์ห้าชั่วโมง หรือโปรเจกต์ห้าเดือนก็ได้ — บอกไม่ได้เลย และผู้พิจารณาจะไม่เสียเวลามาเดาให้
โปรเจกต์เดียวกัน โค้ดเดียวกันด้วยซ้ำ เขียนใหม่ด้วยโครงสร้างสี่ส่วนจากไดอะแกรมด้านบน:
Problem
I wanted a jump that felt as responsive as the platformers I grew up
playing, where a jump pressed slightly after leaving a ledge still works.
Approach
I built a custom CharacterController2D instead of using Unity's
built-in physics jump, so I could control exactly when a jump is
allowed. I added a 0.12-second "coyote time" window after leaving
a platform, and a 0.15-second input buffer so a jump pressed just
before landing still fires the moment you land.
What was hard
My first version buffered the input but did not clear it correctly,
so a single jump press could fire twice if the player landed and then
walked off a second ledge quickly. I found the bug by logging every
jump event with a timestamp and noticing two fired within one frame.
I fixed it by clearing the buffer the instant a jump is consumed,
instead of only clearing it after the buffer window expired.
What I would do differently
I would add a small automated test for the buffer-clearing logic.
I found the double-jump bug by manually playing the game for about
twenty minutes, which is not a reliable way to catch a timing bug
this specific, and I got lucky that I noticed it at all.
ไม่มีอะไรในโปรเจกต์เบื้องหลังเปลี่ยนไปเลยระหว่างสองเวอร์ชัน — jump เดียวกัน บัคเดียวกัน fix เดียวกัน สิ่งที่เปลี่ยนคือเวอร์ชันที่สองให้ผู้พิจารณามีสี่อย่างที่จับต้องได้แยกกันไว้ตัดสิน: การตัดสินใจออกแบบจริง (coyote time กับ input buffering เรียกชื่อเจาะจง), บัคจริงพร้อมสาเหตุจริง, วิธี debug จริง (log พร้อม timestamp ไม่ใช่เดาส่ง) และความตระหนักรู้ในตัวเองจริงว่ากระบวนการที่ดีกว่าควรเป็นแบบไหน แต่ละอย่างมีค่ากับการตัดสินใจจ้างงานมากกว่าประโยค "มันมี jumping และ enemies" มาก
ส่วน "what was hard" สมควรได้รับความใส่ใจเป็นพิเศษ เพราะมันมักเป็นส่วนที่น่าเชื่อถือที่สุดใน write-up ทั้งหมด และเป็นส่วนที่มือใหม่มักถูกล่อใจให้ข้ามไป การเรียกชื่อความยากจริง ๆ อย่างซื่อสัตย์ น่าเชื่อถือกับผู้พิจารณาที่มีประสบการณ์มากกว่าการอ้างว่าทุกอย่างราบรื่น — เพราะทุกคนที่เคยส่งงานจริงรู้ว่าไม่มีอะไรราบรื่นสมบูรณ์แบบเลย และผู้สมัครที่อ้างว่าราบรื่นอ่านแล้วดูเหมือนไม่มีประสบการณ์ หรือไม่ซื่อสัตย์เต็มที่เกี่ยวกับกระบวนการของตัวเอง บัคที่เจาะจงและอธิบายดี ๆ เป็นสัญญาณ portfolio ที่แข็งแรงกว่าโปรเจกต์ที่อ้างว่าไม่มีบัคเลย
โค้ดของคุณมีผู้อ่านสองแบบ และทั้งคู่ต้องสำเร็จได้แทบไม่ต้องออกแรง ไม่งั้นชิ้นงานจะถูกหักคะแนนไม่ว่ากลไกเบื้องหลังจะดีแค่ไหน ผู้อ่านคนแรกสแกนโค้ดคุณประมาณหกสิบวินาทีโดยไม่รันมัน อ่านอย่างเดียว ผู้อ่านคนที่สอง clone repository แล้วลอง build และรันจริง ๆ หัวข้อ 9 พูดถึงวิธีเขียนอธิบายโปรเจกต์ หัวข้อนี้พูดถึงการทำให้ตัวโปรเจกต์เองรอดจากผู้อ่านทั้งสองแบบ
อันนี้ compile ได้ ทำงานได้ และทำตามที่ write-up แข็งแรงในหัวข้อ 9 อธิบายไว้เป๊ะ ๆ แต่มันยังเป็นไฟล์ portfolio ที่แย่ เพราะไม่มีใครสแกนหกสิบวินาทีแล้วบอกได้ว่ามันทำอะไร
public class GC : MonoBehaviour {
public float f = 9.8f;
bool g = false;
void Update(){
if(Input.GetKeyDown(KeyCode.Space)){
g=!g;
Physics.gravity = g ? new Vector3(0,f,0) : new Vector3(0,-f,0);
}
}
}
ผู้พิจารณาที่เห็นโค้ดนี้ต้อง reverse-engineer ก่อนว่า GC, f, และ g หมายถึงอะไร ก่อนจะเริ่มตัดสินได้ด้วยซ้ำว่า logic ดีหรือไม่ดี เวลา reverse-engineer นั้นหักออกมาจากหกสิบวินาทีที่หัวข้อ 1 บอกว่าคุณมีโดยตรง
public class GravityController : MonoBehaviour
{
[SerializeField] float gravityStrength = 9.8f;
bool isFlipped = false;
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
FlipGravity();
}
// Flips world gravity between pointing down (normal) and up (flipped).
void FlipGravity()
{
isFlipped = !isFlipped;
float direction = isFlipped ? 1f : -1f;
Physics.gravity = new Vector3(0f, gravityStrength * direction, 0f);
}
}
logic เดียวกัน จำนวนไอเดียจริงเท่ากัน แต่ผู้พิจารณาอ่านชื่อ class ชื่อ field และคอมเมนต์เดียวได้แล้วเข้าใจว่าไฟล์นี้ทำอะไร ก่อนที่จะต้องอ่าน method body อย่างละเอียดด้วยซ้ำ ชื่อที่ดีกับคอมเมนต์หนึ่งจุดที่วางไว้ถูกที่ทำงานเกือบทั้งหมดตรงนี้ — คอมเมนต์อธิบาย ทำไม ternary ถึงเลือกทิศทางแบบนั้น ซึ่งไม่ชัดเจนจากโค้ดอย่างเดียว แทนที่จะแค่พูดซ้ำสิ่งที่บรรทัดนั้นบอกอยู่แล้ว
// set isFlipped to the opposite of isFlipped เหนือบรรทัด isFlipped = !isFlipped; คอมเมนต์แบบนั้นเพิ่มเวลาอ่านโดยไม่เพิ่มข้อมูลอะไรที่โค้ดยังไม่บอกอยู่แล้ว เก็บคอมเมนต์ไว้สำหรับส่วนที่ไม่ชัดเจนจากโค้ดเอง — "ทำไม" ไม่ใช่ "ทำอะไร"README คือไฟล์ที่ repository โชว์เป็นอันดับแรก และสำหรับ portfolio piece มันควรตอบตามลำดับ: นี่คืออะไร หน้าตาเป็นยังไง เล่นหรือรันยังไง และผู้เขียนเรียนรู้อะไร ผู้พิจารณาที่ clone repository คุณแล้วไม่เจอ README เลย หรือเจอแต่ชื่อโปรเจกต์อย่างเดียว ไม่มีทางรู้ได้เลยว่าโปรเจกต์นี้ทำงานได้จริงไหมโดยไม่ขุดหาไฟล์ source เอง — และตามหัวข้อ 1 เขาจะไม่ทำแบบนั้น
# Gravity Snap
A small puzzle-platformer where the player flips gravity to solve
rooms. Built solo in three weeks with Unity 2022 (C#).
[ GIF of the gravity-flip mechanic goes here ]
## Play it
- Browser build: https://username.itch.io/gravity-snap
- Video walkthrough: https://youtube.com/watch?v=xxxxxxx
## What this project is
- 8 hand-built levels
- a custom gravity-flip controller, see Assets/Scripts/GravityController.cs
- a small level editor used to place gravity zones, see Assets/Editor/
## How to run it from source
1. Clone the repo
2. Open the project with Unity 2022.3 LTS
3. Open Scenes/MainMenu.unity and press Play
## What was hard
Making the gravity flip feel instant but not disorienting. See the
"What was hard" section of the itch.io writeup for the three
iterations I tried before landing on the current version.
## What I would change
The level editor works, but only I can use it comfortably. It needs
on-screen labels before anyone else could use it without me
explaining every button first.
สังเกตว่า README เอาโครงสร้างสี่ส่วนเดียวกันจากหัวข้อ 9 มาใช้ซ้ำในสอง heading สุดท้าย นี่เป็นความตั้งใจ ไม่ใช่บังเอิญ — พอคุณเขียน problem, approach, difficulty, และ reflection ครั้งเดียวสำหรับ write-up บน itch.io หรือเว็บไซต์แล้ว การเอาโครงสร้างเดียวกันมาใช้ซ้ำใน README แทบไม่เสียแรงอะไรเพิ่มเลย และมันทำให้สองพื้นที่ที่สำคัญที่สุดของโปรเจกต์คุณ (หน้าที่ผู้พิจารณาเปิดเจอ กับ repository ที่เขาคลิกเข้าไป) เล่าเรื่องไปในทิศทางเดียวกัน
สถานที่ที่คุณเอาชิ้นงานที่เสร็จแล้วไปวางสำคัญพอ ๆ กับการทำมันให้เสร็จเลย เพราะสถานที่ที่ผิดเพิ่ม friction ที่หยุดผู้พิจารณาก่อนที่เขาจะได้เห็นงานคุณด้วยซ้ำ มีสามที่โฮสต์ที่ครอบคลุมสิ่งที่ portfolio ของมือใหม่ต้องการเกือบทั้งหมด
GitHub ยังสำคัญในฐานะจุดยึดทางเทคนิค — เป็นที่ที่ผู้พิจารณาที่สนใจจริง ๆ ไปอ่านโค้ดของคุณ ตามที่ timeline ในหัวข้อ 1 บอกไว้ตรงนาที 1:30 แต่สังเกตว่ามันอยู่ตรงไหนใน timeline นั้น — หลังจากวิดีโอกับ GIF ทำให้ผู้พิจารณาสนใจแล้ว ไม่ใช่แทนที่มัน ลิงก์ GitHub ที่ไม่มี playable build ไม่มีภาพอะไรแนบมาเลยเท่ากับขอให้ผู้พิจารณาจินตนาการเองทั้งหมด ซึ่งคนส่วนใหญ่ไม่ทำ
เหตุผลพื้นฐานที่ playable build หรือวิดีโอชนะข้อความยาว ๆ นั้นง่ายมาก: มันคือการพิสูจน์ด้วยการสาธิต ไม่ใช่พิสูจน์ด้วยคำกล่าวอ้าง ข้อความบอกได้ว่า "movement มัน responsive" GIF แค่โชว์ movement แล้วให้ผู้พิจารณาตัดสินเองว่าจริงไหม — ซึ่งทั้งน่าเชื่อถือกว่า เพราะเขาสรุปความเห็นเอง และประเมินได้เร็วกว่า เพราะตัดสินคลิปสามวินาทีใช้แรงน้อยกว่าอ่านและเชื่อย่อหน้าข้อความ
ทุกอย่างในหัวข้อนี้จนถึงตอนนี้พูดถึงชิ้นงานที่เสร็จแล้ว มีหลักฐานอีกแบบหนึ่งที่เสริมกันได้ดีที่ควรสร้างควบคู่กันไป: ร่องรอยสาธารณะของ devlog (อัปเดตสั้น ๆ ที่โพสต์สม่ำเสมอโชว์โปรเจกต์ระหว่างที่ยังสร้างไม่เสร็จ) โพสต์ไว้ที่ไหนสักที่ที่มองเห็นได้ เช่นหน้า devlog บน itch.io, บล็อกส่วนตัว, หรือบัญชีโซเชียล
ชิ้นงานที่เสร็จแล้วพิสูจน์ว่าคุณทำของเสร็จได้ ร่องรอย devlog พิสูจน์สิ่งที่ชิ้นงานเสร็จชิ้นเดียวพิสูจน์ไม่ได้: คุณรักษาความพยายามต่อเนื่องได้หลายสัปดาห์ ทำงานต่อได้แม้กลไกยังรู้สึกผิดอยู่ และรับ feedback ระหว่างทางได้ ไม่ใช่โชว์งานหลังจากขัดเกลาเสร็จแล้วเท่านั้น พฤติกรรมเหล่านี้แหละคือสิ่งที่งานจริงต้องการทุกวัน มากกว่าโมเมนต์ "ship it" ที่ดราม่าจุดเดียวที่ portfolio piece ที่เสร็จแล้วจับภาพไว้ ผู้พิจารณาที่เห็นทั้งชิ้นงานที่เสร็จแล้วและร่องรอยรก ๆ รายสัปดาห์ที่นำไปสู่มัน จะได้ภาพที่ครบถ้วนและน่าเชื่อถือกว่าอย่างใดอย่างหนึ่งเพียงลำพัง
ทุกอย่างข้างบนเป็นเรื่องเยอะที่จะจำพร้อมกัน เลยขอสรุปเป็นลำดับที่ทำได้จริงลำดับหนึ่ง สร้างขึ้นจากเหตุผลในหัวข้อก่อนหน้าโดยตรง
แผนนี้ตั้งใจ scope เล็กในทุกขั้นตอน มือใหม่ที่พยายามสร้างโปรเจกต์ทะเยอทะยานหนึ่งชิ้นให้เป็น portfolio ทั้งหมดกำลังรับความเสี่ยงแบบเดียวกับที่หัวข้อ 3 เตือนไว้พอดี: โปรเจกต์ทะเยอทะยานที่ไม่เสร็จหนึ่งชิ้นมีค่าน้อยกว่าโปรเจกต์เล็กที่เสร็จสามชิ้น ต่อให้ไอเดียของโปรเจกต์ทะเยอทะยานฟังดูดีกว่าก็ตาม เล็กและเสร็จ สามรอบ ด้วยสูตร presentation เดียวกันทุกครั้ง ทั้งเป็นแผนที่ทำได้จริงตามตารางเวลาของมือใหม่ และตามเหตุผลตลอดทั้งหัวข้อนี้ เป็น portfolio ที่แข็งแรงกว่าด้วย
ไม่มีคำตอบที่ถูกต้องเพียงหนึ่งเดียว — แบบฝึกหัดนี้เช็คว่าสี่ส่วนปรากฏครบตามลำดับ แต่ละส่วนมีเนื้อหาเจาะจงและจับต้องได้แทนคำกล่าวอ้างคลุมเครือ นี่คือหนึ่งเวอร์ชันที่สมเหตุสมผล:
Problem
Our level designer was spending about fifteen minutes per level manually
renaming and re-numbering exported level files so they would sort
correctly inside the engine, a pure copy-and-rename task with no
creative work in it at all.
Approach
I built an editor window in Unity that scans a folder of level files,
detects their current order from a data file, and renames and
re-numbers them all in one pass with a single button press. I chose
to read the intended order from a small JSON file instead of the
filenames themselves, so the designer could reorder levels without
ever touching a filename by hand.
What was hard
My first version renamed files in place one at a time, which
occasionally caused a naming collision partway through the batch --
for example, renaming file 3 to file 4 before file 4 had been moved
out of the way yet, silently overwriting it. I fixed it by renaming
every file to a temporary name first, then renaming all of them to
their final names in a second pass, so no two files ever collide.
What I would do differently
I would add a confirmation preview showing the old and new names
side by side before applying any rename, since the current version
has no undo and a mistake requires restoring from version control.
เทียบกับคำอธิบายต้นฉบับประโยคเดียว: เวอร์ชันเขียนใหม่ให้ผู้พิจารณามีปัญหาจริงพร้อมตัวเลข (สิบห้านาทีต่อ level), การตัดสินใจออกแบบจริงพร้อมเหตุผล (ไฟล์ JSON กำหนดลำดับแทนชื่อไฟล์), บัคจริงพร้อมสาเหตุจริงและ fix จริง (บัค rename ชนกัน) และความตระหนักรู้ในตัวเองจริงหนึ่งอย่าง (ยังไม่มี undo) เวอร์ชันเขียนใหม่ไหนก็ตามที่มีเนื้อหาสี่แบบนี้ครบ ต่อให้รายละเอียดที่คิดขึ้นเองต่างกัน ก็ผ่านแบบฝึกหัดนี้
1. Platformer tutorial follow-along, exact copy, unfinished
(stuck on enemy AI).
2. A dash mechanic with proper cancel windows, fully polished,
video recorded, code readable.
3. A Tetris clone, fully finished and bug-free, but no README,
no video, no screenshots -- just raw source code.
4. A shader experiment that compiles but looks identical to a
flat unlit material -- no visible technique to point to.
5. A level-loading tool, finished, with a 40-second demo video
showing the manual workflow next to the tool's workflow.
6. A multiplayer lobby system, about 70 percent done, still has
netcode bugs that must be fixed before it is usable.
7. A rim-light shader, finished, with before/after screenshots
and a written explanation of the technique.
8. A racing game clone, fully finished, but nearly identical to
its tutorial with only one small feature (a boost pad) added.
เก็บ #2 (dash mechanic), #5 (level-loading tool), และ #7 (rim-light shader)
ทั้งสามชิ้นมีคุณสมบัติที่หัวข้อ 3 ต้องการ: เสร็จแล้ว และเสร็จพร้อมสูตร presentation เต็มจากหัวข้อ 8 ที่ใช้ไปแล้วหรือใช้ได้ชัดเจน (วิดีโอ, before/after, คำอธิบายเป็นลายลักษณ์อักษร) รวมกันแล้วทั้งสามชิ้นยังครอบคลุมสามสายจากหัวข้อ 4 ถึง 7 ซึ่งโชว์ความหลากหลายได้โดยไม่เจือจาง quality ตรงไหนเลย
อีกห้าชิ้นถูกตัด แต่ละชิ้นมีเหตุผลเจาะจงที่ระบุได้ ไม่ใช่แค่ "ไม่ดีเท่า":
คำตอบอีกแบบที่สมเหตุสมผลคือสลับเอา #3 มาแทนหนึ่งในสามชิ้น ถ้าคุณยอมเสียเวลาสักบ่ายเขียน README กับอัดวิดีโอสั้น ๆ ก่อน — คุณภาพของโปรเจกต์เบื้องหลังมีอยู่แล้ว แค่ presentation ยังขาดไปเท่านั้น
# Inventory System
A small inventory system for an RPG prototype: add and remove items,
automatically stack duplicate items into one slot with a count, and
display everything in a grid UI. Built with Unity 2022 (C#).
[ GIF of picking up items and seeing them stack goes here ]
## Play it
- Browser build: https://username.itch.io/inventory-demo
- Video walkthrough: https://youtube.com/watch?v=yyyyyyy
## What this project is
- add/remove items through a simple ItemDatabase, see
Assets/Scripts/Inventory.cs
- automatic stacking for duplicate item types, up to a max
stack size defined per item
- a grid UI that updates live as the inventory changes, see
Assets/Scripts/InventoryUI.cs
## How to run it from source
1. Clone the repo
2. Open the project with Unity 2022.3 LTS
3. Open Scenes/Demo.unity and press Play
4. Walk over the colored cubes to pick up sample items
## What was hard
Keeping the UI in sync with the data without rebuilding the whole
grid every frame. See the "What was hard" section of the itch.io
writeup for how I moved to an event-based update instead of a
per-frame refresh.
## What I would change
Item stacking currently has no per-item maximum stack size in the
data -- every item stacks to 99. I would move max stack size into
the ItemDatabase so different item types could have different limits.
สิ่งที่คำตอบนี้เช็ค นอกเหนือจากแค่กรอก template ให้ครบ: คำอธิบายหนึ่งบรรทัดบนสุดบอกจริง ๆ ว่าระบบทำอะไร (เพิ่ม/ลบ, การ stack, grid UI) แทนที่จะแค่บอกชื่อ ส่วน "how to run it" เจาะจงพอที่ใครสักคนจะทำตามได้จริงโดยไม่ต้องเดาว่าต้องเปิด scene ไหน และ "what I would change" เรียกชื่อข้อจำกัดจริงเจาะจง (ไม่มี max stack size ต่อไอเทม) แทนคำว่า "น่าจะดีกว่านี้ได้" ที่คลุมเครือ — ความซื่อสัตย์แบบเดียวกับที่หัวข้อ 9 ต้องการใน write-up เต็มก็ตกทอดมาสู่ README ด้วย