มาชี้จุด optimize ที่ผมเพิ่งรู้วันนี้แหละ คือ “UI Layout Rebuild” ครับ ทำเรื่อง UGUI มาตั้งแต่สมัย 4.6 แล้วแท้ๆ เพิ่งรู้เรื่องนี้วันนี้… จะให้ดี ไปอ่านนี่ให้หมดครับ

A guide to optimizing Unity UI - Unity
Optimizing a user interface driven by Unity UI is an art. Hard-and-fast rules are rare; instead, each situation must be…unity3d.com

เหมือนเขาเขียนใหม่ด้วยจำได้ว่าแต่ก่อนไม่ดีเท่านี้

ใน UI tree เนี่ยจะมีสิ่งที่เรียกว่าอัพเดท layout อยู่ ซึ่งตอนเราขยับ UI นี่ก็ช่วยไม่ได้ครับ (เช่นตอนเข้า ออก ฉาก) แต่ว่า! ทำไมระหว่างเล่นมัน layout อย่างหนักหน่วงทุกเฟรมเลยล่ะ! วิธีดูคือใน timeline มันจะมาต่อท้ายเป็นสีม่วงๆตรงบริเวณ UI แบบนั้นครับ ดูกราฟแรกแบบนี้ท่าจะแย่ เป็น Layout ไปกว่า 90% ของงาน UI ทั้งหมด ผิดปกติแน่ๆ (อันนี้ deep profile เลขเวลาอาจจะเว่อร์ อย่าสนใจ)

ที่เห็นนี่ไม่ใช่น้อยๆเลย รู้สึกจะซัก 15% ของ logic หลักได้… เทียบกับกราฟที่สองที่ปรับดีๆแล้วอันนี้ค่อยน่าดูหน่อย (นี่ไม่ deep)

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

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

อะไรเกิด layout rebuild?

  • เอาตาม common sense ก็ถ้าขยับอะไรแล้วมีลูกๆอยู่ก็ต้องคิดตำแหน่งลูกๆใหม่เป็นยวงลงไป อันนี้ make sense
  • ถ้าไม่มีลูก แต่ขยับ transform หรือ scale จะไม่เกิด layout change
  • ถ้าไม่มีลูก แต่ animate ค่าสี (เช่นใช้ alpha ทำให้หายๆ มาๆ วูบวาบ) อันนี้เกิด layout change เฉยเลยทั้งๆที่ไม่มีลูก งง เกมผมดันทำหลอดไฟเล็กๆ กระพริบอยู่ใน UI โดนเต็มๆ 55555 หลอดไฟเฮงซวย
  • ข้อข้างบน รู้สึกว่าถ้าอะไรที่ทำให้ข้อมูล vertex เปลี่ยน (ค่าสีนี้รู้สึกจะเก็บเป็น vertex color) มันน่าจะทำให้เกิด “graphic rebuild” มากกว่า แต่ไม่รู้ทำไมมันไปขึ้นในกราฟ layout ด้วย (การ scale transform rotate ค่า vertex ยังเหมือนเดิม แค่เปลี่ยน transform matrix)
  • ดังนั้นการเปลี่ยนภาพ การ set enable ให้ภาพหายหรือกลับมา ก็เกิด layout rebuild
  • ขยับกล้องตัวไหนที่ผูกกับ Canvas ที่สั่งให้ตามกล้องนี่จะไม่เกิด rebuild แต่จะเสียค่า Render เยอะขึ้น (สีฟ้า ในกราฟเขียวฟ้า) ทั้งๆที่ UI ขยับตามกล้องแล้วเหมือนไม่เปลี่ยนอะไรก็ตาม ไม่รู้ทำไม

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

แก้ไงดี

  • คิด animation ใหม่ที่ไม่ใช้ alpha/color เช่นอยากให้หายไปก็พับ scale เป็น 0 แทน
  • แยกไป canvas อื่นเวลามันดิ้นจะได้ไม่รบกวนชาวบ้าน จะไม่สามารถ batch draw call กับ canvas อื่นได้นะครับแต่ถ้าเกมยังไม่ GPU Bound จะ draw call +1 ก็ไม่ใช่ปัญหา เอา CPU ให้ได้ต่ำกว่า 16ms ให้ได้ก่อน อีกอย่าง draw call count ไม่ใช่ทุกอย่าง เช่น มี draw call 3 แต่เป็น draw call ละภาพเดียว กับอีกอัน draw call 1 วาดทั้งฉากที่มาจาก sprite sheet แผ่นเดียวกัน อันนี้ 1 draw call จะหนักกว่า ดังนั้นแยกเศษตัวปัญหาไปวาดเดี่ยวๆ 1 draw call ก็อย่าคิดว่ามันเป็นภาระสังคมมากก็ได้ครับ (ดีไม่ดีในอนาคตหาอะไรไปเติมให้ draw call นั้นคุ้มขึ้นได้ให้มันดิ้นไปด้วยกัน พาตัวปัญหาสังคมไปสุมรวมกัน)
  • แต่ทำแบบนั้นจะ animate มากับกรอบต่างๆที่กะจะให้อยู่นิ่งๆทีหลังยาก เพราะต้องแยกมาเป็น tree ใหม่ แล้วกลายเป็นว่าต้อง animate สองอันให้ขนานกันเหมือนเป็นพ่อลูกกัน
  • เทคนิคก็คือให้มันมาด้วยกันก่อน พอกรอบนิ่งแล้วแงะมันออกไปที่อื่น ผมเขียน “TextFlatten” มาไว้ทำหน้าที่นี้โดยเฉพาะ ชื่อ text ก็จริงเพราะตอนแรกทำมาไว้แงะ text แต่เวอร์ชั่นนี้ใช้กับอะไรก็ได้ที่อยากแงะไป canvas อื่น https://github.com/5argon/E7Unity/tree/master/TextFlatten เวลาจังหวะเหมาะๆ พร้อมฟังก์ชั่นโยนกลับที่เดิมเวลาอยากให้ตามกรอบตอนจะเปลี่ยนฉาก เป็นต้น
  • ตัวเลขใช้ TextMeshPro ที่เป็นแบบไม่ UGUI จะไม่เกิด layout rebuild ใดๆทั้งสิ้น ดิ้นตามใจชอบ
  • ภาพ UI ที่กะจะดิ้นเอาไปเป็น SpriteRenderer แทน Image ที่ต้องพึ่ง CanvasRenderer แทนได้มั้ย

รู้ส่วนมากจะไม่อยากทำงั้นเพราะจะเสียพลัง anchor ไป แต่อย่าลืมว่าถึงใช้ SpriteRenderer ไม่ใช่ว่าเราต้องทิ้ง RectTransform ! ก็แปะมันไว้อย่างนั้นใช้แทน Transform ได้

แต่ที่เสียจริงๆคือสมบัติของ Image ที่ว่ายืดภาพตาม RectTransform ได้เพราะมันคุยกันรู้เรื่อง ใช้ ​ SpriteRenderer + RectTransform งี้มันเอาแค่จุดกลางมาใช้ ทีเหลือเหมือนเดิม (จะทำ rect ใหญ่ยังไง SpriteRenderer ก็ไม่สน จะกรอบเล็ก 1px มันก็จะดูแค่ scale transform อย่างเดียว)

แล้วก็เสียอีกที่ที่ต้องเล็งตำแหน่งจอให้ถูกแบบ manual ถ้าเคยใช้ Canvas แบบตามกล้อง แล้วมันเกาะขอบกล้องได้ทุกขอบ ฯลฯ อันนี้ก็ต้องทำระบบ layout แบบบ้านๆมาเสริมด้วย (หรือจะใช้ TextFlatten ที่ว่าแงะออกมาทีหลังก็ได้ ให้ SpriteRenderer แฝงตัวไปกับ UI chain ตอนแรกก่อน พอ animate พอใจแล้วจะเริ่มเกมที่ไม่อยากให้แลค ค่อยเอาออกมา)