Script Optimization - zzragida/UnityDocs GitHub Wiki

๊ฒŒ์ž„์˜ค๋ธŒ์ ํŠธ ๋ ˆํผ๋Ÿฐ์Šค ์บ์‹œ

  • Start/Awake ํ•จ์ˆ˜์—์„œ ์˜ค๋ธŒ์ ํŠธ ์ •๋ณด๋ฅผ ์„ค์ •ํ•œ ํ›„์— Update๋‚˜ FixedUpdate ํ•จ์ˆ˜๋‚ด์—์„œ ์‚ฌ์šฉ
// Reference cache
// Bad
void Update()
{
  GameObject player = GameObject.Find("Player");
  // something with player ...
}
 
// Good
void Start()
{
  player = GameObject.FindWithTag(Tags.Player);
}
  
void Update()
{
  // something with player ...
}

Tag ํ™œ์šฉ

  • Find ํ•จ์ˆ˜๋ณด๋‹ค๋Š” FindGameObjectWithTag๋‚˜ FindObjectType ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉ
  • GameObject compareTo ํ•จ์ˆ˜์‹œ์—๋„ tag ํ™œ์šฉ
  • ํ•˜๋“œ์ฝ”๋”ฉ๋œ tag๋Š” ํ”ผํ•˜๊ณ  ์ƒ์ˆ˜๋กœ ์ •์˜ํ•ด์„œ ํ™œ์šฉ
// Using tag
// Bad
if (gameobject.CompareTag("Player"))
{
  // something ...
}
 
var player = GameObject.FindObject("Player");
 
// Good
if (gameobject.CompreTag(Tags.Player))
{
  // something ...
}
 
var player = GameObject.FindObjectWithTag(Tags.Player);

invokeํ•จ์ˆ˜ ๋Œ€์‹ ์— ์ฝ”๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•˜๋ผ(Use coroutines instead of Invoke())

  • Invoke ํ•จ์ˆ˜๋Š” C#์˜ ๋ฆฌํ”Œ๋ ‰์…˜ ๊ธฐ๋Šฅ์„ ํ™œ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ด ์ข‹์ง€ ์•Š์Œ. ๊ผญ ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ฉด Coroutine์œผ๋กœ ๋Œ€์ฒดํ•ด์„œ ์‚ฌ์šฉํ•˜์ž
// Use coroutines instead of Invoke
// Bad
public void Function()
{
  // something ...
}
Invoke("Function", 3.0f);
 
// Good
public IEnumerator Function(float delay)
{
  yield return new WaitForSeconds(delay);
  // something ...
}
StartCoroutine(Function(3.0f));

์ง€์†๋œ ํ–‰๋™๋งŒ ๊ฐฑ์‹ ํ•˜๋Š” ๋™์ž‘์ด๋ผ๋ฉด ์ฝ”๋ฃจํ‹ด์„ ์‚ฌ์šฉํ•˜๋ผ(Using coroutines for relaxed updates)

  • ์ง€์ •๋œ ์‹œ๊ฐ„์— ๊ณ„์†ํ•ด์„œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ํ™œ์šฉํ•˜๋ฉด ์ข‹์Œ
  • ex) ๋ชฌ์Šคํ„ฐ ์Šคํฐ
// Interval with Start()
// Bad
void Update()
{
  // Perform an action every frame
}
 
// Good 
IEnumerator Start()
{
  while (true)
  {
    // Do something every quarter of second
    yield return new WaitForSeconds(0.25f);
  }
}

๊ณ ์ • ์—…๋ฐ์ดํŠธ ์‹œ๊ฐ„์„ ์ตœ์†Œํ•œ์œผ๋กœ ์ค„์—ฌ๋†“์•„๋ผ(Reduce physics calculations by changing the fixed time step)

  • Edit => Project Settings => Time
  • TimeManager์˜ Fixed Timestep์„ ๊ฒŒ์ž„์— ์ง€์žฅ์ด ์—†๋Š” ์ˆ˜์ค€๊นŒ์ง€ ๋†’๊ฒŒ ์„ค์ •ํ•˜๋ฉด ์„ฑ๋Šฅ์— ๋„์›€์ด ๋จ

์•„๋ฌด๋Ÿฐ ๋™์ž‘์„ ํ•˜์ง€์•Š๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜๋Š” ์ œ๊ฑฐํ•˜๋ผ(Remove empty callbacks)

  • ์•„๋ฌด๋Ÿฐ ๋™์ž‘์„ ํ•˜์ง€ ์•Š๋Š” ํ•จ์ˆ˜๋“ค์€ ์ œ๊ฑฐ ํ•˜์ž. ํŠนํžˆ, Awake(), Start(), Update() ...

๊ผญ ํ•„์š”ํ•œ ์ƒํ™ฉ์—์„œ๋งŒ ๋ถ€ํ•˜๊ฐ€ ํฐํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œ์ผœ๋ผ(Only execute expensive operations occasionally, e.g. Physics.Raycast)

  • Only execute expensive operations occasionally
// Bad
 // Bullet.js
 var speed = 5.0;
  
 function FixedUpdate () {
    var distanceThisFrame = speed * Time.fixedDeltaTime;
    var hit : RaycastHit;
  
    // every frame, we cast a ray forward from where we are to where we will be next frame
    if(Physics.Raycast(transform.position, transform.forward, hit, distanceThisFrame)) {
        // Do hit
    } else {
        transform.position += transform.forward * distanceThisFrame;
    }
 }
 
// Good
 // BulletOptimized.js
 var speed = 5.0;
 var interval = 0.4; // this is 'n', in seconds.
  
 private var begin : Vector3;
 private var timer = 0.0;
 private var hasHit = false;
 private var timeTillImpact = 0.0;
 private var hit : RaycastHit;
  
 // set up initial interval
 function Start () {
    begin = transform.position;
    timer = interval+1;
 }
  
 function Update () {
    // don't allow an interval smaller than the frame.
    var usedInterval = interval;
    if(Time.deltaTime > usedInterval) usedInterval = Time.deltaTime;
  
    // every interval, we cast a ray forward from where we were at the start of this interval
    // to where we will be at the start of the next interval
    if(!hasHit && timer >= usedInterval) {
        timer = 0;
        var distanceThisInterval = speed * usedInterval;
  
        if(Physics.Raycast(begin, transform.forward, hit, distanceThisInterval)) {
            hasHit = true;
            if(speed != 0) timeTillImpact = hit.distance / speed;
        }
  
        begin += transform.forward * distanceThisInterval;
    }
  
    timer += Time.deltaTime;
  
    // after the Raycast hit something, wait until the bullet has traveled
    // about as far as the ray traveled to do the actual hit
    if(hasHit && timer > timeTillImpact) {
        // Do hit
    } else {
        transform.position += transform.forward * speed * Time.deltaTime;
    }
 }

๋ฒกํ„ฐ์˜ ํฌ๊ธฐ๋ฅผ ๋น„๊ตํ• ๋•Œ๋Š” sqrMagnitude ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ผ(Use sqrMagnitude for comparing vector magnitudes)

// Use sqrMagnitude for comparing vector magnitudes
// Bad
if (Vector3.Distance(transform.position, targetPos) < maxDistance)
{
  // Perform an action
}
 
// Vector3.magnitude property
if ((transform.position - targetPos).magnitude < maxDistance)
{
  // Perform an action
}
 
// Good
if ((transform.position - targetPos).sqrMagnitude < maxDistance * maxDistance)
{
  // Perform an action
}

์˜ค๋ธŒ์ ํŠธ ํ’€์„ ์‚ฌ์šฉํ•˜๋ผ(Use object pooling)

  • ์œ ์šฉํ•œ ์—์…‹์„ ํ™œ์šฉ

๋ฐ•์‹ฑ/์–ธ๋ฐ•์‹ฑ์„ ํ”ผํ•˜๋ผ(Avoid boxing/unboxing)

  • Stack Area <=> Heap Area
  • ๊ฐ’(Value), ์ฐธ์กฐ(Reference) ๊ฐ์ฒด์˜ ์ฐจ์ด ์ดํ•ด

๊ณ„์‚ฐ๋ฃจํ‹ด ํšจ์œจ์„ฑ

  • ๋‚˜๋ˆ—์…ˆ๋ณด๋‹ค๋Š” ๊ณฑ์…ˆ์ด CPU๋ถ€ํ•˜๊ฐ€ ์ž‘์Œ
// Minimize callstack overhead in inner loops
// Bad
x = 100.0f / 2.0f;
 
// Good
x = 100.0f * 0.5f;

๋‚ด๋ถ€ ๋ฃจํ”„์•ˆ์—์„œ ํ•จ์ˆ˜ํ˜ธ์ถœ์„ ์ตœ์†Œํ™” ํ•˜๋ผ(Minimize callstack overhead in inner loops)

// Minimize callstack overhead in inner loops
// Bad
for (;;) 
{
  x = Mathf.Abs(x);
}
 
// Good
for (;;)
{
  x = (x > 0 ? x : -x);
}