└── README.md /README.md: -------------------------------------------------------------------------------- 1 | Unity-CSharp-Optimize-Guildline 2 | ============================= 3 | 4 | - [盡可能的讓判斷條件不要在迴圈中](#盡可能的讓判斷條件不要在迴圈中) 5 | - [只有在資料改變時再執行方法](#只有在資料改變時再執行方法) 6 | - [避免逐幀計算](#避免逐幀計算) 7 | - [使用快取](#使用快取) 8 | - [避免使用 LINQ](#避免使用-linq) 9 | - [避免使用下列 Unity API](#避免使用下列-unity-api) 10 | - [使用 GameObject.CompareTag 取代 GameObject.tag](#使用-gameobjectcomparetag-取代-gameobjecttag) 11 | - [使用 yield return null 取代 yield return 0](#使用-yield-return-null-取代-yield-return-0) 12 | - [減少 Vector 計算](#減少-vector-計算) 13 | - [盡可能使用 Transform.localPosition](#盡可能使用-transformlocalposition) 14 | - [減少取得 Transform.position、Transform.localPosition](#減少取得-transformpositiontransformlocalposition) 15 | - [避免使用 foreach](#避免使用-foreach) 16 | - [盡量使用 Array 取代 List](#盡量使用-array-取代-list) 17 | - [避免使用 Enum 函式](#避免使用-enum-函式) 18 | - [避免使用屬性 Property](#避免使用屬性-property) 19 | - [使用 is 或 as 而不是強制類型轉換](#使用-is-或-as-而不是強制類型轉換) 20 | - [避免大量使用 MonoBehaviour.Update、FixedUpdate、LateUpdate](#避免大量使用-monobehaviourupdatefixedupdatelateupdate) 21 | - [大量字元串接時使用 String.Concat](#大量字元串接時使用-stringconcat) 22 | - [大量字串串接時使用 StringBuilder.Append](#大量字串串接時使用-stringbuilderappend) 23 | - [生成大量相同物件使用 Object Pool](#生成大量相同物件使用-object-pool) 24 | - [使用 struct 取代 class](#使用-struct-取代-class) 25 | - [避免使用解構子](#避免使用解構子) 26 | - [盡可能預先快取組件](#盡可能預先快取組件) 27 | - [看不到物件時,關閉不需要執行的組件](#看不到物件時關閉不需要執行的組件) 28 | - [設定 Shader 參數時,使用 PropertyToID](#設定-shader-參數時使用-propertytoid) 29 | - [設定 Animator 參數時,使用 StringToHash](#設定-animator-參數時使用-stringtohash) 30 | - [預先定義 List 或 Dictionary 的初始化大小](#預先定義-list-或-dictionary-的初始化大小) 31 | - [資料來源](#資料來源) 32 | 33 | # 盡可能的讓判斷條件不要在迴圈中 34 | 調整前 35 | ```csharp 36 | private void Update() 37 | { 38 | for (int i = 0; i < array.Length; i++) 39 | { 40 | if (exampleBool) 41 | { 42 | ExampleFunction(array[i]); 43 | } 44 | } 45 | } 46 | ``` 47 | 48 | 調整後 49 | ```csharp 50 | private void Update() 51 | { 52 | if (exampleBool) 53 | { 54 | for (int i = 0; i < array.Length; i++) 55 | { 56 | ExampleFunction(array[i]); 57 | } 58 | } 59 | } 60 | ``` 61 | 62 | # 只有在資料改變時再執行方法 63 | ## 案例1 64 | 調整前 65 | ```csharp 66 | private int m_score; 67 | 68 | public void AddScore(int value) 69 | { 70 | m_score += value; 71 | } 72 | 73 | private void Update() 74 | { 75 | DisplayScore(m_score); 76 | } 77 | ``` 78 | 79 | 調整後 80 | ```csharp 81 | private int m_score; 82 | 83 | public void AddScore(int value) 84 | { 85 | m_score += value; 86 | DisplayScore(m_score); 87 | } 88 | ``` 89 | 90 | ## 案例2 91 | 調整前 92 | ```csharp 93 | private void Update() 94 | { 95 | ExampleGarbageGeneratingFunction(transform.position.x); 96 | } 97 | ``` 98 | 99 | 調整後 100 | ```csharp 101 | private float m_curPosX; 102 | private float m_lastPosX; 103 | 104 | private void Update() 105 | { 106 | m_curPosX = transform.position.x; 107 | if (m_lastPosX != m_curPosX) 108 | { 109 | m_lastPosX = m_curPosX; 110 | ExampleGarbageGeneratingFunction(m_lastPosX); 111 | } 112 | } 113 | ``` 114 | 115 | # 避免逐幀計算 116 | 調整前 117 | ```csharp 118 | private void Update() 119 | { 120 | ExampleExpensiveFunction(); 121 | } 122 | ``` 123 | 124 | 調整後 125 | ```csharp 126 | private int m_interval = 3; 127 | 128 | private void Update() 129 | { 130 | if (Time.frameCount % m_interval == 0) 131 | { 132 | ExampleExpensiveFunction(); 133 | } 134 | } 135 | ``` 136 | 137 | # 使用快取 138 | 應盡量避免生成參考類型物件,即任何 new XXXXX(),否則會導致記憶體分配 139 | 140 | ## 案例 GetComponent 141 | 調整前 142 | ```csharp 143 | private void Update() 144 | { 145 | Renderer renderer = GetComponent(); 146 | ExampleFunction(renderer); 147 | } 148 | ``` 149 | 150 | 調整後 151 | ```csharp 152 | private Renderer m_renderer; 153 | 154 | private void Awake() 155 | { 156 | m_renderer = GetComponent(); 157 | } 158 | 159 | private void Update() 160 | { 161 | ExampleFunction(m_renderer); 162 | } 163 | ``` 164 | 165 | ## 案例 FindObjectsOfType 166 | 調整前 167 | ```csharp 168 | private void OnTriggerEnter(Collider other) 169 | { 170 | Renderer[] renderers = FindObjectsOfType(); 171 | ExampleFunction(renderers); 172 | } 173 | ``` 174 | 175 | 調整後 176 | ```csharp 177 | private Renderer[] m_renderers; 178 | 179 | private void Awake() 180 | { 181 | m_renderers = FindObjectsOfType(); 182 | } 183 | 184 | private void OnTriggerEnter(Collider other) 185 | { 186 | ExampleFunction(m_renderers); 187 | } 188 | ``` 189 | 190 | ## 案例 List 191 | 調整前 192 | ```csharp 193 | private void Update() 194 | { 195 | List list = new List(); 196 | PopulateList(list); 197 | } 198 | ``` 199 | 200 | 調整後 201 | ```csharp 202 | private List m_list = new List(); 203 | void Update() 204 | { 205 | m_list.Clear(); 206 | PopulateList(m_list); 207 | } 208 | ``` 209 | 210 | ## 案例 new WaitForSeconds 211 | 調整前 212 | ```csharp 213 | while (!isDone) 214 | { 215 | yield return new WaitForSeconds(1f); 216 | } 217 | ``` 218 | 219 | 調整後 220 | ```csharp 221 | WaitForSeconds delay = new WaitForSeconds(1f); 222 | 223 | while (!isDone) 224 | { 225 | yield return delay; 226 | } 227 | ``` 228 | 229 | ## 案例 transform 230 | 調整前 231 | ```csharp 232 | public void UpdateCharacter() 233 | { 234 | var lastPos = transform.position; 235 | transform.position = lastPos 236 | + wantedVelocity * (speed * speedFactor 237 | * Mathf.Sin(someOtherFactor) 238 | * drag * friction * Time.deltaTime); 239 | } 240 | ``` 241 | 242 | 調整後 243 | ```csharp 244 | private Transform m_transform; 245 | 246 | private void Awake() 247 | { 248 | m_transform = transform; 249 | } 250 | 251 | public void UpdateCharacter() 252 | { 253 | var lastPos = m_transform.position; 254 | m_transform.position = lastPos 255 | + wantedVelocity * (speed * speedFactor 256 | * Mathf.Sin(someOtherFactor) 257 | * drag * friction * Time.deltaTime); 258 | } 259 | ``` 260 | 261 | ## 案例 Time.deltaTime 262 | 調整前 263 | ```csharp 264 | public void UpdateCharacter() 265 | { 266 | float factor = speed * speedFactor 267 | * Mathf.Sin(someOtherFactor) 268 | * drag * friction * Time.deltaTime; 269 | 270 | m_lastPos.x += wantedVelocity.x * factor; 271 | m_lastPos.y += wantedVelocity.y * factor; 272 | m_lastPos.z += wantedVelocity.z * factor; 273 | m_transform.localPosition = m_lastPos; 274 | } 275 | ``` 276 | 277 | 調整後 278 | ```csharp 279 | private float m_deltaTime; 280 | 281 | public void UpdateCharacter() 282 | { 283 | float factor = speed * speedFactor 284 | * Mathf.Sin(someOtherFactor) 285 | * drag * friction * m_deltaTime; 286 | 287 | m_lastPos.x += wantedVelocity.x * factor; 288 | m_lastPos.y += wantedVelocity.y * factor; 289 | m_lastPos.z += wantedVelocity.z * factor; 290 | m_transform.localPosition = m_lastPos; 291 | } 292 | ``` 293 | 294 | # 避免使用 LINQ 295 | 雖然 LINQ 簡潔易讀寫,但通常需要更多計算及記憶體配置 296 | 297 | # 避免使用下列 Unity API 298 | - [GameObject.SendMessage](https://docs.unity3d.com/ScriptReference/GameObject.SendMessage.html) 299 | - [GameObject.oadcastMessage](https://docs.unity3d.com/ScriptReference/GameObject.BroadcastMessage.html) 300 | - [GameObject.Find](https://docs.unity3d.com/ScriptReference/GameObject.Find.html) 301 | - [GameObject.FindGameObjectsWithTag](https://docs.unity3d.com/ScriptReference/GameObject.FindGameObjectsWithTag.html) 302 | - [GameObject.FindWithTag](https://docs.unity3d.com/ScriptReference/GameObject.FindWithTag.html) 303 | - [Object.FindObjectOfType](https://docs.unity3d.com/ScriptReference/Object.FindObjectOfType.html) 304 | - [Object.FindObjectsOfType](https://docs.unity3d.com/ScriptReference/Object.FindObjectsOfType.html) 305 | - [Camera.main](https://docs.unity3d.com/ScriptReference/Camera-main.html) 306 | 307 | # 使用 GameObject.CompareTag 取代 GameObject.tag 308 | 調整前 309 | ```csharp 310 | private const string TAG_PLAYER = "Player"; 311 | 312 | void OnTriggerEnter(Collider other) 313 | { 314 | bool isPlayer = other.gameObject.tag == TAG_PLAYER; 315 | } 316 | ``` 317 | 318 | 調整後 319 | ```csharp 320 | private const string TAG_PLAYER = "Player"; 321 | 322 | void OnTriggerEnter(Collider other) 323 | { 324 | bool isPlayer = other.gameObject.CompareTag(TAG_PLAYER); 325 | } 326 | ``` 327 | 328 | # 使用 yield return null 取代 yield return 0 329 | 調整前 330 | ```csharp 331 | yield return 0; 332 | ``` 333 | 334 | 調整後 335 | ```csharp 336 | yield return null; 337 | ``` 338 | 339 | # 減少 Vector 計算 340 | ## 案例1 341 | 調整前 342 | ```csharp 343 | public void UpdateCharacter() 344 | { 345 | var lastPos = transform.position; 346 | transform.position = lastPos 347 | + wantedVelocity * speed * speedFactor 348 | * Mathf.Sin(someOtherFactor) 349 | * drag * friction * Time.deltaTime; 350 | } 351 | ``` 352 | 353 | 調整後 354 | ```csharp 355 | public void UpdateCharacter() 356 | { 357 | var lastPos = transform.position; 358 | transform.position = lastPos 359 | + wantedVelocity * (speed * speedFactor 360 | * Mathf.Sin(someOtherFactor) 361 | * drag * friction * Time.deltaTime); 362 | } 363 | ``` 364 | 365 | ## 案例2 366 | 調整前 367 | ```csharp 368 | public void UpdateCharacter() 369 | { 370 | m_lastPos += wantedVelocity * (speed * speedFactor 371 | * Mathf.Sin(someOtherFactor) 372 | * drag * friction * Time.deltaTime); 373 | m_transform.localPosition = m_lastPos; 374 | } 375 | ``` 376 | 377 | 調整後 378 | ```csharp 379 | public void UpdateCharacter() 380 | { 381 | float factor = speed * speedFactor 382 | * Mathf.Sin(someOtherFactor) 383 | * drag * friction * Time.deltaTime; 384 | 385 | m_lastPos.x += wantedVelocity.x * factor; 386 | m_lastPos.y += wantedVelocity.y * factor; 387 | m_lastPos.z += wantedVelocity.z * factor; 388 | m_transform.localPosition = m_lastPos; 389 | } 390 | ``` 391 | 392 | # 盡可能使用 Transform.localPosition 393 | 調整前 394 | ```csharp 395 | public void UpdateCharacter() 396 | { 397 | var lastPos = m_transform.position; 398 | m_transform.position = lastPos 399 | + wantedVelocity * (speed * speedFactor 400 | * Mathf.Sin(someOtherFactor) 401 | * drag * friction * Time.deltaTime); 402 | } 403 | ``` 404 | 405 | 調整後 406 | ```csharp 407 | public void UpdateCharacter() 408 | { 409 | var lastPos = m_transform.localPosition; 410 | m_transform.localPosition= lastPos 411 | + wantedVelocity * (speed * speedFactor 412 | * Mathf.Sin(someOtherFactor) 413 | * drag * friction * Time.deltaTime); 414 | } 415 | ``` 416 | 417 | # 減少取得 Transform.position、Transform.localPosition 418 | 調整前 419 | ```csharp 420 | public void UpdateCharacter() 421 | { 422 | var lastPos = m_transform.localPosition; 423 | m_transform.localPosition = lastPos 424 | + wantedVelocity * (speed * speedFactor 425 | * Mathf.Sin(someOtherFactor) 426 | * drag * friction * Time.deltaTime); 427 | } 428 | ``` 429 | 430 | 調整後 431 | ```csharp 432 | private Vector3 m_lastPos = Vector3.zero; 433 | 434 | public void UpdateCharacter() 435 | { 436 | m_lastPos += wantedVelocity * (speed * speedFactor 437 | * Mathf.Sin(someOtherFactor) 438 | * drag * friction * Time.deltaTime); 439 | m_transform.localPosition = m_lastPos; 440 | } 441 | ``` 442 | 443 | # 避免使用 foreach 444 | 調整前 445 | ```csharp 446 | foreach(int value in m_list) 447 | { 448 | DoSomething(value); 449 | } 450 | ``` 451 | 452 | 調整後 453 | ```csharp 454 | int length = m_list.Count; 455 | for(int i = 0; i < length; i++) 456 | { 457 | DoSomething(m_list[i]); 458 | } 459 | ``` 460 | 461 | 數據比較 (執行次數 100000) 462 | | | Time ms | GC Alloc | 463 | |---------|---------|----------| 464 | | foreach | 28.04 | 48 B | 465 | | for | 17.47 | 0 B | 466 | 467 | # 盡量使用 Array 取代 List 468 | 調整前 469 | ```csharp 470 | int length = m_list.Count; 471 | for(int i = 0; i < length; i++) 472 | { 473 | DoSomething(m_list[i]); 474 | } 475 | ``` 476 | 477 | 調整後 478 | ```csharp 479 | int length = m_array.Length; 480 | for (int i = 0; i < length; i++) 481 | { 482 | DoSomething(m_array[i]); 483 | } 484 | ``` 485 | 486 | 數據比較 (執行次數 100000) 487 | | | Time ms | GC Alloc | 488 | |-------|---------|----------| 489 | | List | 17.44 | 0 B | 490 | | Array | 8.70 | 0 B | 491 | 492 | # 避免使用 Enum 函式 493 | 調整前 494 | ```csharp 495 | Enum.GetName(typeof(State), stringValue); 496 | Enum.GetName(typeof(State), intValue); 497 | Enum.GetNames(typeof(State)); 498 | Enum.GetValues(typeof(State)); 499 | Enum.Parse(typeof(State), stringValue); 500 | 501 | State result; 502 | Enum.TryParse(stringValue, out result); 503 | 504 | result.ToString(); 505 | ``` 506 | 507 | 調整後 508 | ```csharp 509 | FastEnum.GetName(stringValue); 510 | FastEnum.GetName(intValue); 511 | FastEnum.GetNames(); 512 | FastEnum.GetValues(); 513 | FastEnum.Parse(stringValue); 514 | 515 | State result; 516 | FastEnum.TryParse(stringValue, out result); 517 | 518 | FastEnum.ToString((int)result); 519 | ``` 520 | 521 | 數據比較 (執行次數 100000)
522 | Enum > FastEnum 523 | | | Time ms | GC Alloc | 524 | |-----------------|-----------------|-----------------| 525 | | GetName(string) | 339.60 > 18.30 | 1.9 MB > 0 B | 526 | | GetName(int) | 662.15 > 18.56 | 3.8 MB > 0 B | 527 | | GetNames | 227.57 > 12.99 | 3.8 MB > 0 B | 528 | | GetValues | 821.96 > 15.63 | 8.8 MB > 1.0 KB | 529 | | Parse | 765.91 > 184.60 | 12.2 MB > 0 B | 530 | | TryParse | 761.73 > 185.73 | 12.2 MB > 0 B | 531 | | ToString | 579.02 > 18.10 | 3.8 MB > 0 B | 532 | 533 | # 避免使用屬性 Property 534 | 屬性 Property 底層依然是透過方法實現,使用 Property 會導致執行效能較差,但使用上及維護上會較為方便,能夠針對 get 或 set 設置不同的訪問層級和檢查機制,且支援任何方法的語言特性,如 virtual、abstract。 535 | 536 | 調整前 537 | ```csharp 538 | public int intValue { get; set; } 539 | ``` 540 | 541 | 調整後 542 | ```csharp 543 | public int intValue; 544 | ``` 545 | 546 | 數據比較 (執行次數 1000000) 547 | | | Time ms | GC Alloc | 548 | |----------|---------|----------| 549 | | Property | 41.00 | 0 B | 550 | | Field | 0 | 0 B | 551 | 552 | # 使用 is 或 as 而不是強制類型轉換 553 | is: 能夠檢查一個對象是否兼容於其他指定類型,回傳一個 Bool 值且不會跳出異常
554 | as: 作用跟強制類型轉換一樣,但不會跳出異常,如果轉換失敗,會回傳 null 555 | 556 | # 避免大量使用 MonoBehaviour.Update、FixedUpdate、LateUpdate 557 | 由於 Unity MonoBehaviour 使用的是 Messaging System,能夠讓開發者在 MonoBehaviour 中自行定義特殊方法,如: Awake、Start、Update、FixedUpdate、LateUpdate 等。
558 | 當有使用大量 MonoBehaviour.Update、FixedUpdate、LateUpdate 需求時,應自定義功能取代。 559 | 560 | 調整前 561 | ```csharp 562 | using UnityEngine; 563 | public class TestMonoBehaviour : MonoBehaviour 564 | { 565 | private int m_count; 566 | private void Update() 567 | { 568 | m_count++; 569 | } 570 | } 571 | ``` 572 | 573 | 調整後 574 | ```csharp 575 | public class TestOptimizedMonoBehaviour : CoreComponent 576 | { 577 | private int m_count; 578 | public override void Tick(float deltaTime, float unscaledDeltaTime) 579 | { 580 | m_count++; 581 | } 582 | } 583 | ``` 584 | 585 | 數據比較 (執行次數 10000) 586 | | | Time ms | GC Alloc | 587 | |---------------|---------|----------| 588 | | MonoBehaviour | 4.01 | 0 B | 589 | | CoreComponent | 1.82 | 0 B | 590 | 591 | # 大量字元串接時使用 String.Concat 592 | 調整前 593 | ```csharp 594 | char c = 'X'; 595 | string output = string.Empty; 596 | for(int i = 0; i < stringLength; i++) 597 | { 598 | output += c; 599 | } 600 | ``` 601 | 602 | 調整後 603 | ```csharp 604 | char c = 'X'; 605 | char[] chars = new char[stringLength]; 606 | for (int i = 0; i < stringLength; i++) 607 | { 608 | chars[i] = c; 609 | } 610 | 611 | string output = string.Concat(chars); 612 | ``` 613 | 614 | 數據比較 (執行次數 100000) 615 | | | Time ms | GC Alloc | 616 | |---------------|----------|----------| 617 | | String += | 11723.20 | 1.32 GB | 618 | | String.Concat | 27.50 | 3.3 MB | 619 | 620 | # 大量字串串接時使用 StringBuilder.Append 621 | 調整前 - String += 622 | ```csharp 623 | string output = string.Empty; 624 | for(int i = 0; i < count; i++) 625 | { 626 | output += value; 627 | } 628 | ``` 629 | 630 | 調整前 - String.Format 631 | ```csharp 632 | private const string FORMAT = "{0}{1}"; 633 | string output = string.Empty; 634 | for (int i = 0; i < count; i++) 635 | { 636 | output = string.Format(FORMAT, output, value); 637 | } 638 | ``` 639 | 640 | 調整後 641 | ```csharp 642 | m_stringBuilder.Clear(); 643 | for (int i = 0; i < count; i++) 644 | { 645 | m_stringBuilder.Append(value); 646 | } 647 | 648 | string output = m_stringBuilder.ToString(); 649 | ``` 650 | 651 | 數據比較 (執行次數 10000) 652 | | | Time ms | GC Alloc | 653 | |----------------------|---------|----------| 654 | | String += | 563.65 | 477.1 MB | 655 | | String.Format | 1331.72 | 1.07 GB | 656 | | StringBuilder.Append | 0.48 | 97.7 KB | 657 | 658 | # 生成大量相同物件使用 Object Pool 659 | 調整前 660 | ```csharp 661 | GameObject instance = GameObject.Instantiate(m_prefab); 662 | GameObject.Destroy(instance); 663 | ``` 664 | 665 | 調整後 666 | ```csharp 667 | GameObject instance = PoolManager.Instance.Get(m_prefab); 668 | PoolManager.Instance.Recycle(instance); 669 | ``` 670 | 671 | # 使用 struct 取代 class 672 | 調整前 673 | ```csharp 674 | class VectorClass 675 | { 676 | public int X { get; set; } 677 | public int Y { get; set; } 678 | } 679 | 680 | private void Execute() 681 | { 682 | VectorClass[] vectors = new VectorClass[10000]; 683 | for (int i = 0; i < vectors.Length; i++) 684 | { 685 | vectors[i] = new VectorClass(); 686 | vectors[i].X = 5; 687 | vectors[i].Y = 10; 688 | } 689 | } 690 | ``` 691 | 692 | 調整後 693 | ```csharp 694 | struct VectorStruct 695 | { 696 | public int X { get; set; } 697 | public int Y { get; set; } 698 | } 699 | 700 | private void Execute() 701 | { 702 | VectorStruct[] vectors = new VectorStruct[10000]; 703 | for (int i = 0; i < vectors.Length; i++) 704 | { 705 | vectors[i].X = 5; 706 | vectors[i].Y = 10; 707 | } 708 | } 709 | ``` 710 | 711 | 數據比較 (執行次數 10000) 712 | | | Time ms | GC Alloc | 713 | |--------|---------|----------| 714 | | class | 5.68 | 312.5 KB | 715 | | struct | 2.00 | 78.2 KB | 716 | 717 | # 避免使用解構子 718 | 調整前 719 | ```csharp 720 | class SimpleWithFinalizer 721 | { 722 | public int x { get; set; } 723 | 724 | ~SimpleWithFinalizer() 725 | { 726 | 727 | } 728 | } 729 | 730 | private void Execute() 731 | { 732 | for (int i = 0; i < 10000; i++) 733 | { 734 | new SimpleWithFinalizer(); 735 | } 736 | } 737 | ``` 738 | 739 | 調整後 740 | ```csharp 741 | class Simple 742 | { 743 | public int x { get; set; } 744 | } 745 | 746 | private void Execute() 747 | { 748 | for (int i = 0; i < 10000; i++) 749 | { 750 | new Simple(); 751 | } 752 | } 753 | ``` 754 | 755 | 數據比較 (執行次數 10000) 756 | | | Time ms | GC Alloc | 757 | |------------|---------|----------| 758 | | 有解構子 | 4.48 | 195.3 KB | 759 | | 沒有解構子 | 2.14 | 195.3 KB | 760 | 761 | # 盡可能預先快取組件 762 | 調整前 763 | ```csharp 764 | [SerializeField] protected Transform m_transform = null; 765 | [SerializeField] protected RectTransform m_rectTransform = null; 766 | [SerializeField] protected Canvas m_canvas = null; 767 | [SerializeField] protected CanvasScaler m_canvasScaler = null; 768 | [SerializeField] protected GraphicRaycaster m_graphicRaycaster = null; 769 | [SerializeField] protected CanvasGroup m_canvasGroup = null; 770 | [SerializeField] protected ScrollRect[] m_scrollRects = null; 771 | [SerializeField] protected InputField[] m_inputFields = null; 772 | 773 | private void Awake() 774 | { 775 | m_transform = transform; 776 | m_rectTransform = GetComponent(); 777 | m_canvas = GetComponent(); 778 | m_canvasScaler = GetComponent(); 779 | m_graphicRaycaster = GetComponent(); 780 | m_canvasGroup = GetComponent(); 781 | m_scrollRects = GetComponentsInChildren(true); 782 | m_inputFields = GetComponentsInChildren(true); 783 | } 784 | ``` 785 | 786 | 調整後 787 | ```csharp 788 | [SerializeField] protected Transform m_transform = null; 789 | [SerializeField] protected RectTransform m_rectTransform = null; 790 | [SerializeField] protected Canvas m_canvas = null; 791 | [SerializeField] protected CanvasScaler m_canvasScaler = null; 792 | [SerializeField] protected GraphicRaycaster m_graphicRaycaster = null; 793 | [SerializeField] protected CanvasGroup m_canvasGroup = null; 794 | [SerializeField] protected ScrollRect[] m_scrollRects = null; 795 | [SerializeField] protected InputField[] m_inputFields = null; 796 | 797 | protected override void CacheComponents() 798 | { 799 | CacheComponent(ref m_transform); 800 | CacheComponent(ref m_rectTransform); 801 | CacheComponent(ref m_canvas); 802 | CacheComponent(ref m_canvasScaler); 803 | CacheComponent(ref m_graphicRaycaster); 804 | CacheComponent(ref m_canvasGroup); 805 | CacheComponentsInChildren(ref m_scrollRects, true); 806 | CacheComponentsInChildren(ref m_inputFields, true); 807 | } 808 | ``` 809 | 810 | # 看不到物件時,關閉不需要執行的組件 811 | 當物件存在於場景中,即使物件在可視範圍之外其身上的組件也會持續運作,當這類組件的數量變多時,就會開始影響遊戲性能。
812 | 建議當物件在視錐體之外或沒有顯示在畫面上時,關閉將下列組件。 813 | 814 | - CanvasScaler 815 | - ScrollRect 816 | - InputField 817 | - DynamicBone 818 | - 其他任何昂貴的 MonoBehaviour 819 | 820 | # 設定 Shader 參數時,使用 PropertyToID 821 | Unity 執行後會給予 Shader 參數名稱一個唯一的識別符。
822 | 比起使用參數名稱傳遞資料,使用 [Shader.PropertyToID](https://docs.unity3d.com/ScriptReference/Shader.PropertyToID.html) 更為高效。 823 | 824 | 調整前 825 | ```csharp 826 | private const string _Color = "_Color"; 827 | private void SetColor(Color value) 828 | { 829 | m_material.SetColor(_Color, value); 830 | } 831 | ``` 832 | 833 | 調整後 834 | ```csharp 835 | private readonly int _Color = Shader.PropertyToID("_Color"); 836 | private void SetColor(Color value) 837 | { 838 | m_material.SetColor(_Color, value); 839 | } 840 | ``` 841 | 842 | 數據比較 (執行次數 100000) 843 | | | Time ms | GC Alloc | 844 | |----------------------------------------------|---------|----------| 845 | | Material.SetColor(string name, Color value) | 44.38 | 0 B | 846 | | Material.SetColor(int nameID, Color value) | 29.83 | 0 B | 847 | 848 | # 設定 Animator 參數時,使用 StringToHash 849 | 同理 [Shader.PropertyToID](https://docs.unity3d.com/ScriptReference/Shader.PropertyToID.html),使用 [Animator.StringToHash](https://docs.unity3d.com/ScriptReference/Animator.StringToHash.html) 更為高效。 850 | 851 | 調整前 852 | ```csharp 853 | private const string _BoolParameterName = "_BoolParameterName"; 854 | private void SetBoolParameter(bool value) 855 | { 856 | m_animator.SetBool(_BoolParameterName, value); 857 | } 858 | ``` 859 | 860 | 調整後 861 | ```csharp 862 | private readonly int _BoolParameterName = Animator.StringToHash("_BoolParameterName"); 863 | private void SetBoolParameter(bool value) 864 | { 865 | m_animator.SetBool(_BoolParameterName, value); 866 | } 867 | ``` 868 | 869 | 數據比較 (執行次數 100000) 870 | | | Time ms | GC Alloc | 871 | |--------------------------------------------|---------|----------| 872 | | Animator.SetBool(string name, bool value) | 19.86 | 0 B | 873 | | Animator.SetBool(int nameID, bool value) | 16.78 | 0 B | 874 | 875 | # 預先定義 List 或 Dictionary 的初始化大小 876 | 877 | 調整前 878 | ```csharp 879 | private void InitializeList(int count) 880 | { 881 | m_list = new List(); 882 | for(int i = 0; i < count; i++) 883 | { 884 | m_list.Add(i); 885 | } 886 | } 887 | 888 | private void InitializeDictionary(int count) 889 | { 890 | m_dictionary = new Dictionary(); 891 | for(int i = 0; i < count; i++) 892 | { 893 | m_dictionary.Add(i, i); 894 | } 895 | } 896 | ``` 897 | 898 | 調整後 899 | ```csharp 900 | private void InitializeList(int count) 901 | { 902 | m_list = new List(count); 903 | for(int i = 0; i < count; i++) 904 | { 905 | m_list.Add(i); 906 | } 907 | } 908 | 909 | private void InitializeDictionary(int count) 910 | { 911 | m_dictionary = new Dictionary(count); 912 | for(int i = 0; i < count; i++) 913 | { 914 | m_dictionary.Add(i, i); 915 | } 916 | } 917 | ``` 918 | 919 | 數據比較 (執行次數 100000) 920 | | | Time ms | GC Alloc | 921 | |-------------------------|---------|----------| 922 | | List 無初始化大小 | 16.77 | 1.0 MB | 923 | | List 有初始化大小 | 8.72 | 390.7 KB | 924 | | Dictionary 無初始化大小 | 40.42 | 5.8 MB | 925 | | Dictionary 有初始化大小 | 36.50 | 2.1 MB | 926 | 927 | # 資料來源 928 | - [Fixing Performance Problems](https://learn.unity.com/tutorial/fixing-performance-problems#5c7f8528edbc2a002053b595) 929 | - [對 Unity 的效能建議 - Mixed Reality](https://docs.microsoft.com/zh-tw/windows/mixed-reality/develop/unity/performance-recommendations-for-unity) 930 | - [Unite 2016 - Tools, Tricks and Technologies for Reaching Stutter Free 60 FPS in INSIDE](https://www.youtube.com/watch?v=mQ2KTRn4BMI) 931 | - [10000 Update() calls](https://blogs.unity3d.com/2015/12/23/1k-update-calls/) 932 | - [8 Techniques to Avoid GC Pressure and Improve Performance in C# .NET](https://michaelscodingspot.com/avoid-gc-pressure/) 933 | --------------------------------------------------------------------------------