วันนี้พูดถึงเรื่องการเขียนเกมสักหน่อย พอดีว่าผมเคยเขียนเกมมาก่อนตอนสมัยทำงานใหม่ ๆ และตอนนี้ผมเริ่มอยากกลับมาเขียนเกมอีกแล้วก็เลยต้องรื้อฟื้นสักหน่อยครับ

Game Object ก็คือวัตถุที่อยู่ในเกม เป็นส่วนของลอจิคของเกม ตรงนี้จะรวมตั้งแต่ ตัวละครที่ผู้เล่นบังคับ ตัวละครที่ผู้เล่นไม่ได้บังคับ (NPC ทั้งหลาย รวมทั้งพวกศัตรู) วัตถุพวกกระสุนปืน หรือแม้กระทั่งฉากและอื่น ๆ เราจะมองว่าโปรแกรมเกมคือชุดของวัตถุในเกมและการปฎิสัมพันธ์กันระหว่างวัตถุก็ได้ครับ

โดยทั่วไป โปรแกรมประเภทเกมจะมีลักษณะแบบนี้ครับ

while(true) {
  Input input = ReadInput();
  ProcessGameObjects(input);
  DrawGameObjects();
}

ผมละรายละเอียดเกี่ยวกับการออกจากลูปนะครับ

ในอดีตขณะที่เรามี CPU แกนเดียว การใช้ Thread เดียวร่วมกันทั้งโปรแกรมเป็นวิธีที่ดีที่สุด เพราะว่าต่อให้เราแยก Thread ไปมันก็ต้องสลับกันทำงานไปมา Performance ก็แย่ลง และไม่มีข้อดีอะไรจากการแยก Thread ดังนั้นเขียนโปรแกรมเป็นอนุกรมจะง่ายที่สุด

ดังนั้นฟังก์ชั่น ProcessGameObjects() ก็จะมีหน้าตาประมาณนี้ครับ

void ProcessGameObjects(const Input& input) {
  for(auto& obj : GetGameObjects()) {obj.Process(input);}
}

วิธีนี้จริง ๆ ไม่ได้มีข้อเสียอะไรนะ เพียงแต่ว่าโปรแกรมจะทำงานอยู่บนแกนเดียวของ CPU ซึ่งนั่นคือการไม่ได้ใช้งานอีก 75% ที่เหลือของระบบที่มี 4 แกน มันก็ดูน่าเสียดายนิดหน่อยล่ะครับ

ผมเคยคิดว่า ถ้าเราจะแยกให้แต่ละ object ทำงานอยู่บน thread ของตัวเองไปเลยจะดีมั้ย แล้วก็แยกออกมาจาก Render Loop ไปเลยด้วย คราวนี้เกมของเราก็จะมี thread เต็มไปหมด เกิดผมมีวัตถุในเกมประมาณสัก 100 ชิ้น ก็จะมี 100 thread ทำงานพร้อมๆ กัน

ตัว Game Object ก็อาจจะมีสภาพเป็น ... แบบนี้

void GameObject::Process() {
  while(true) {
    Input input = ReadInput();
    switch(input.type) {
       //calculating things.
    }
  }   
}

CreateThread([](gameobject.Process());).run();    
while(true) {
  DrawGameObjects();
}

ปัญหาของวิธีนี้คือคราวนี้เราจะมี thread เยอะมากจนควบคุมได้ยาก มีปัญหาเรื่อง synchronization ระหว่าง thread ที่วาดวัตถุขึ้นจอ กับ thread ของการประมวลผล และความจริงคือการสลับ thread ไปมาก็มี penalty เช่นกัน คราวนี้กลายเป็นว่ามี thread เยอะเกินไปจนทำให้ตัวโปรแกรมช้าลง

วิธีข้างบนผมไม่แนะนำให้ใช้นะครับ ปวดหัว 555

วิธีต่อไปจะเป็นการเปลี่ยน Game Loop แบบธรรมดาให้เป็น multi-thread แทน เป็นวิธีง่าย ๆ ครับ

while(true) {
  Input input = ReadInput();
  ProcessGameObjects(input);
  DrawGameObjects();
}

void ProcessGameObjects(const Input& input) {
  ThreadPool threadPool;
  for(auto& obj : GetGameObjects()) {
    threadPool.CreateThread([&input](){obj.Process(input);});
  }
  threadPool.WaitForAll();
}

วิธีนี้เป็นการแยกการคำนวนแต่ละวัตถุไปเป็นแต่ละ thread สั่งให้มันทำงาน แล้วรอจนกว่ามันจะทำงานเสร็จ จากนั้นก็ค่อยวาดแต่ละวัตถุขึ้นจอ วิธีนี้ดีตรงที่ว่ามันควบคุมได้ง่ายกว่ามาก ถ้าเราจะหยุดไม่ให้การคำนวนวัตถุทำงาน ก็แค่ไม่เรียก Process() ให้ทำงาน แค่นั้นเอง (เทียบกับวิธีบนที่ต้องสร้าง flag ขึ้นมาใหม่แล้วปวดหัวกว่ามาก) แต่ปัญหาอื่น ๆ ก็ยังมีมาให้ปวดหัวอยู่บ้างเหมือนกัน แต่ผมคิดว่าอันนี้ทำงานกับมันง่ายกว่าครับ

มันก็ยังมีประเด็นเรื่องของจำนวน thread มากเกินไป และการสลับไปมานั้นมันก็ค่อนข้างแพง ผมคิดว่าการบังคับให้มีจำนนวน thread น้อย ๆ นั้นจะให้ผลที่ดีกว่าเพราะว่าเรามีจำนวนแกนของซีพียูจำกัดนั่นแหละ

void ProcessGameObjects(const Input& input) {
  constexpr int maxThreadCount = 4;

  ThreadList threadPool;
  auto gameObjects = GetGameObjects();
  auto iter = gameObjects.first();

  while(iter != gameObjects.last()) {
    if(threadList.GetActiveThreadCount() < maxThreadCount && iter != gameObjects.end() {
      auto &object = *iter;
      threadPool.CreateThread([&input](){object.Process(input);});
      ++iter;
    }
  }
  threadPool.WaitForAll();
}

อันนี้คือการจำกัดไม่ให้มี thread เกินกว่า 4 thread ทำงานพร้อม ๆ กันในเวลาเดียว ซึ่งจะทำงานกับระบบที่มี 4 แกนได้ดีกว่าการทำงานทีเดียวพร้อม ๆ กันเป็น 100 ครับ :)

ข้อเสียคือมันก็ยังมีเรื่องของ Synchronization ให้ปวดหัวอยู่ดีนั่นแหละ แต่ว่ามันจะง่ายกว่าการแยกออกจาก draw thread ไปเลยมากครับ (แต่การแยกออกจาก draw thread ไปเลยก็มีข้อดีของมันนะ)

โพสต์นี้เป็นการตอบกลับบล็อกของคุณ khachoji 5 สิ่งที่เจ้านายควรเข้าใจ "โปรแกรมเมอร์" ให้มากกว่านี้ คือ ผมมีความเห็นใกล้เคียงกับคุณเอ็มระดับหนึ่งนะ ก็เลยว่าจะเขียนเรื่องนี้บ้าง (ว่ากันง่าย ๆ คือก็ลอกการบ้านนั่นแหละ)

ก่อนอื่นจะขอเริ่มจากเรื่องที่ไม่เกี่ยวข้องกันก่อน ส่วนตัวผมจะแบ่งโปรแกรมเมอร์ออกเป็น 2 กลุ่ม

  1. พวก Shinso หรือพวกเลือดแท้ พวกนี้เป็นโปรแกรมเมอร์กันถึงระดับวิญญาณ เป็นพวกที่มีชีวิตอยู่เพื่อการสร้างสรรค์ผลงาน มีแรงบันดาลใจเป็นแรงขับเคลื่อน
  2. พวก Shito หรือพวกสาวก พวกนี้จริง ๆ แล้วไม่ได้อยากเป็นโปรแกรมเมอร์กันหรอก แต่ว่าถูกความสำเร็จของพวกเลือดแท้ดึงดูดเข้ามา มีผลประโยชน์เป็นแรงขับเคลื่อน

เอ่อ ... ชักจะเริ่มเลอะเทอะไปใหญ่ (ศัพท์พวกนี้มาจากเกม Tsukihime ครับ) คือผมจะบอกว่าจริง ๆ แล้วคนที่เป็นโปรแกรมเมอร์เองก็มักจะแบ่งเป็นคนที่ชอบการสร้างสรรค์ผลงาน ทำเพราะว่ามันสนุก ทำเพราะว่าใจรัก กับคนที่ไม่ใช่ ซึ่งสิ่งที่ผมจะพูดถึงต่อไปเนี่ยคิดว่ามันจะเกี่ยวกับคนกลุ่มแรกมากกว่ากลุ่มที่สอง

ส่วนผมเป็นพวกกลุ่มไหนเหรอ ? ... เอ่อ ไม่รู้สิ ผมบอกได้แค่ว่าถ้าผมไม่เขียนโปรแกรม ก็นึกไม่ออกเหมือนกันว่าจะทำอะไร เพราะว่าเขียนอยู่ทุกวัน 555

ทีนี้ถ้าถามว่า สิ่งที่พวกเราโปรแกรมเมอร์ต้องการในการทำงานเนี่ย มีอะไรบ้าง ผมคิดว่าโดยรวม ๆ แล้วเราก็ต้องการอยู่สามอย่าง ก็คือ พื้นที่ ความเข้าใจ และแรงสนับสนุน

พื้นที่

คำว่า "พื้นที่" ที่ผมพูดถึง ไม่ใช่แค่พื้นที่ในเชิงรูปธรรม แต่หมายรวมทั้งพื้นที่ในเชิงนามธรรมด้วย

งานเขียนโปรแกรมเป็นงานศิลปะครับ คือ ตัวโปรแกรม หรือตัวคอมพิวเตอร์ มันเป็นผลงานวิศวกรรมก็จริงนะ แต่ว่าการเขียนโค๊ดเนี่ยมันจะมีการตัดสินใจเข้ามาเกี่ยวข้องมากมาย (เพราะเรากำลังสั่งให้คอมพิวเตอร์ทำงานอยู่น่ะครับ) และการตัดสินใจเนี่ยมันเป็นศิลปะที่ยากที่สุดแขนงนึงเลยนะ ดังนั้นพวกเราโปรแกรมเมอร์ในระดับหนึ่งก็เป็นศิลปินอยู่ และการทำงานศิลปะนั้นต้องใช้พื้นที่ค่อนข้างกว้างกว่างานหลาย ๆ ประเภท

ดังนั้น ในแง่ของพื้นที่ สิ่งแรกที่โปรแกรมเมอร์ต้องการคือ "อำนาจการตัดสินใจ" ครับ เพราะว่าเราต้องตัดสินใจในเรื่องมากมาย (แม้กระทั่งการตั้งชื่อลูก!) ดังนั้นถ้าเกิดว่ามีเรื่องใดเรื่องหนึ่งที่ต้องพึ่งพาอาศัยคนอื่นในการตัดสินใจ เช่น การเปลี่ยนพฤติกรรมของโปรแกรม ถ้าเกิดว่าเราต้องรอคนอื่นงานมันจะล้าช้ามาก และนั่นคือสิ่งสุดท้ายที่คุณต้องการใช่ไหมครับ

สิ่งที่สองก็คือ "สมาธิ" อย่างที่บอกว่าการเขียนโปรแกรมมีการตัดสินใจเข้ามาเกี่ยวข้องเยอะมาก โปรแกรมเมอร์จะต้องคิด จะต้องหาข้อมูลสนับสนุน จะต้องหาข้อมูลประกอบการตัดสินใจ และทั้งหมดเป็นงานที่ต้องใช้สมาธิมหาศาล ฉะนั้นถ้าเกิดว่าคุณทำให้โปรแกรมเมอร์สติหลุดไประหว่างที่กำลังอยู่ในห้วงความคิดนิดเดียว ความคิดตรงนั้นก็จะหลุดหายไปและจะต้องเริ่มคิดใหม่ตั้งแต่ต้น (ซึ่งบ้างครั้งนั่นหมายถึง 15-30 นาทีเลยทีเดียว) แน่นอนว่ามีโปรแกรมเมอร์ที่มีสมาธิแข็ง ๆ จะสามารถสลับความคิดไปมา หรือสามารถกู้คืนความคิดได้อย่างรวดเร็ว แต่โปรแกรมเมอร์ส่วนใหญ่ที่ผมเห็นทำไม่ค่อยได้นะ

สิ่งที่สามคือ "เวลา" ผมเป็นพวกจะต้องหยุดพักทุก ๆ 1-2 ชั่วโมง คือถ้าทำงานต่อเนื่องงานจะออกมาแย่มาก เพราะมันเป็นงานที่ใช้พลังเยอะมาก ผมจะต้องออกไปเดินพักบ้างทุก ๆ 1-2 ชั่วโมง (ถ้าเป็นงานบริษัทผมจะชดเชยโดยการทำงานให้ยาวขึ้นโดยไม่เก็บ OT น่ะนะ) ส่วนอีกหลาย ๆ คนที่ผมเห็นก็จะมีการหยุดพักด้วยวิธีอื่น ๆ เช่น โทรศัพท์คุยกับแฟนบ้าง กดไอแพดเล่นบ้าง หรือแม้กระทั่งเล่นเกมในเวลางาน

ความเข้าใจ

คนมักจะคิดว่าโปรแกรมเมอร์ส่วนใหญ่เป็นคนเข้าใจยาก เพราะเราใช้ศัพท์เฉพาะทางเยอะ และพวกเราส่วนใหญ่ใช้ภาษาอังกฤษได้ดีถึงดีโคตร ๆ (ประชดบ้างนิดหน่อย) ดังนั้นหลาย ๆ ครั้งเราจะพูดไทยคำอังกฤษคำจนคนมักจะคิดว่าเรากระแดะ

คุณอาจจะเจอโปรแกรมเมอร์ที่ติ๊สมาก ๆ จนถึงคนที่แบบทำตัวเหมือนเดิมทุกวัน ๆ จนเหมือนกับหุ่นยนต์

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

ถ้าเกิดคุณดันดวงไม่ดีมีแฟนเป็นโปรแกรมเมอร์ ก็คงต้องทำความเข้าใจกันให้ดี ลองศึกษานิสัยใจคอและทำความเข้าใจกับสิ่งที่เขาเป็น ถ้าเกิดว่ารับไม่ได้คุณอาจจะต้องถอนตัวออกไป แต่ผมพบว่าโปรแกรมเมอร์ส่วนใหญ่นิสัยน่ารักนะ (แต่การแสดงออกอาจจะสอบตกไปสองสามคะแนน)

แรงสนับสนุน

ความยากของงานเขียนโปรแกรมจริง ๆ อยู่ที่การติดตามพัฒนาการของวงการนี้ครับ คือ ในหลาย ๆ วงการการพัฒนาเนี่ยอาจจะเป็นรายปี อาจจะแบบใช้เวลา 1 ปีในการออกแบบเครื่องมือ 1 ปีผลิตเครื่องมือ ทดลองใช้กับสถานการณ์จริง 5 ปี ก่อนจะเริ่มขาย ตอนขายกว่าคนจะซื้อไปใช้ก็อีก 2-3 ปี งานด้านไอทีเนี่ยทุกอย่างรวมกันใช้เวลาไม่ถึงปีถึง 2-3 ปีเท่านั้นเอง ดังนั้นในแต่ละวันจะมีอะไรใหม่ๆ ออกมาเยอะมาก ยิ่งถ้าเป็นเทคโนโลยีที่เป็นเทรนด์ใหม่ ๆ นี่จะวิ่งเร็วแบบตามไม่ทันกันเลย

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

ดังนั้นถ้ามีแรงสนับสนุนให้เขาสามารถพัฒนาตัวเองได้ง่ายและดีขึ้นละก็ โปรแกรมเมอร์รักตายเลย 55

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

คำว่ามากน่ะแค่ไหน ? ผมเคยทำงานที่ใช้เวลา 2 ชม.ให้เสร็จภายใน 30 วินาทีมาแล้ว 😉 (ก็คือการเขียนสคริปท์ขึ้นมาแทนงานทำมือนั่นล่ะครับ ส่วนที่ช้าที่สุดของการทำงานกับโปรแกรมคือส่วนของมนุษย์ทั้งนั้นแหละ)

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

ลองเปิดโอกาสให้เขาได้เสนอพัฒนาการที่เกิดจากการสั่งสมทักษะมาอย่างต่อเนื่อง แล้วมันจะเป็นผลประโยชน์ต่อองค์กรของคุณมากมายทีเดียวล่ะ